1/* 2 * International Telephone Input v7.1.1 3 * https://github.com/jackocnr/intl-tel-input.git 4 * Licensed under the MIT license 5 */ 6// wrap in UMD - see https://github.com/umdjs/umd/blob/master/jqueryPluginCommonjs.js 7(function(factory) { 8 if (typeof define === "function" && define.amd) { 9 define([ "jquery" ], function($) { 10 factory($, window, document); 11 }); 12 } else if (typeof module === "object" && module.exports) { 13 module.exports = factory(require("jquery"), window, document); 14 } else { 15 factory(jQuery, window, document); 16 } 17})(function($, window, document, undefined) { 18 "use strict"; 19 // these vars persist through all instances of the plugin 20 var pluginName = "intlTelInput", id = 1, // give each instance it's own id for namespaced event handling 21 defaults = { 22 // typing digits after a valid number will be added to the extension part of the number 23 allowExtensions: false, 24 // automatically format the number according to the selected country 25 formatAsYouType: false, 26 // if there is just a dial code in the input: remove it on blur, and re-add it on focus 27 autoHideDialCode: true, 28 // add or remove input placeholder with an example number for the selected country 29 autoPlaceholder: true, 30 // append menu to a specific element 31 dropdownContainer: "", 32 // don't display these countries 33 excludeCountries: [], 34 // geoIp lookup function 35 geoIpLookup: null, 36 // initial country 37 initialCountry: "", 38 // don't insert international dial codes 39 nationalMode: true, 40 // number type to use for placeholders 41 numberType: "MOBILE", 42 // display only these countries 43 onlyCountries: [], 44 // the countries at the top of the list. defaults to united states and united kingdom 45 preferredCountries: [ "us", "gb" ], 46 // specify the path to the libphonenumber script to enable validation/formatting 47 utilsScript: "" 48 }, keys = { 49 UP: 38, 50 DOWN: 40, 51 ENTER: 13, 52 ESC: 27, 53 PLUS: 43, 54 A: 65, 55 Z: 90, 56 ZERO: 48, 57 NINE: 57, 58 SPACE: 32, 59 BSPACE: 8, 60 TAB: 9, 61 DEL: 46, 62 CTRL: 17, 63 CMD1: 91, 64 // Chrome 65 CMD2: 224 66 }, windowLoaded = false; 67 // keep track of if the window.load event has fired as impossible to check after the fact 68 $(window).load(function() { 69 windowLoaded = true; 70 }); 71 function Plugin(element, options) { 72 this.element = element; 73 this.options = $.extend({}, defaults, options); 74 this._defaults = defaults; 75 // event namespace 76 this.ns = "." + pluginName + id++; 77 // Chrome, FF, Safari, IE9+ 78 this.isGoodBrowser = Boolean(element.setSelectionRange); 79 this.hadInitialPlaceholder = Boolean($(element).attr("placeholder")); 80 this._name = pluginName; 81 } 82 Plugin.prototype = { 83 _init: function() { 84 // if in nationalMode, disable options relating to dial codes 85 if (this.options.nationalMode) { 86 this.options.autoHideDialCode = false; 87 } 88 // IE Mobile doesn't support the keypress event (see issue 68) which makes formatAsYouType impossible 89 if (navigator.userAgent.match(/IEMobile/i)) { 90 this.options.formatAsYouType = false;
91 } 92 // we cannot just test screen size as some smartphones/website meta tags will report desktop resolutions 93 // Note: for some reason jasmine breaks if you put this in the main Plugin function with the rest of these declarations 94 // Note: to target Android Mobiles (and not Tablets), we must find "Android" and "Mobile" 95 this.isMobile = /Android.+Mobile|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 96 if (this.isMobile) { 97 // trigger the mobile dropdown css 98 $("body").addClass("iti-mobile"); 99 // on mobile, we want a full screen dropdown, so we must append it to the body 100 if (!this.options.dropdownContainer) { 101 this.options.dropdownContainer = "body"; 102 } 103 } 104 // we return these deferred objects from the _init() call so they can be watched, and then we resolve them when each specific request returns 105 // Note: again, jasmine breaks when I put these in the Plugin function 106 this.autoCountryDeferred = new $.Deferred(); 107 this.utilsScriptDeferred = new $.Deferred(); 108 // process all the data: onlyCountries, excludeCountries, preferredCountries etc 109 this._processCountryData(); 110 // generate the markup 111 this._generateMarkup(); 112 // set the initial state of the input value and the selected flag 113 this._setInitialState(); 114 // start all of the event listeners: autoHideDialCode, input keydown, selectedFlag click 115 this._initListeners(); 116 // utils script, and auto country 117 this._initRequests(); 118 // return the deferreds 119 return [ this.autoCountryDeferred, this.utilsScriptDeferred ]; 120 }, 121 /******************** 122 * PRIVATE METHODS 123 ********************/ 124 // prepare all of the country data, including onlyCountries, excludeCountries and preferredCountries options 125 _processCountryData: function() { 126 // process this instance's countries 127 this._processAllCountries(); 128 // process the countryCodes map 129 this._processCountryCodes(); 130 // process the preferredCountries 131 this._processPreferredCountries(); 132 }, 133 // add a country code to this.countryCodes 134 _addCountryCode: function(iso2, dialCode, priority) { 135 if (!(dialCode in this.countryCodes)) { 136 this.countryCodes[dialCode] = []; 137 } 138 var index = priority || 0; 139 this.countryCodes[dialCode][index] = iso2; 140 }, 141 // filter the given countries using the process function 142 _filterCountries: function(countryArray, processFunc) { 143 var i; 144 // standardise case 145 for (i = 0; i < countryArray.length; i++) { 146 countryArray[i] = countryArray[i].toLowerCase(); 147 } 148 // build instance country array 149 this.countries = []; 150 for (i = 0; i < allCountries.length; i++) { 151 if (processFunc($.inArray(allCountries[i].iso2, countryArray))) { 152 this.countries.push(allCountries[i]); 153 } 154 } 155 }, 156 // process onlyCountries or excludeCountries array if present 157 _processAllCountries: function() { 158 if (this.options.onlyCountries.length) { 159 // process onlyCountries option 160 this._filterCountries(this.options.onlyCountries, function(inArray) { 161 // if country is in array 162 return inArray != -1; 163 }); 164 } else if (this.options.excludeCountries.length) { 165 // process excludeCountries option 166 this._filterCountries(this.options.excludeCountries, function(inArray) { 167 // if country is not in array 168 return inArray == -1; 169 }); 170 } else { 171 this.countries = allCountries; 172 } 173 }, 174 // process the countryCodes map 175 _processCountryCodes: function() { 176 this.countryCodes = {}; 177 for (var i = 0; i < this.countries.length; i++) { 178 var c = this.countries[i]; 179 this._addCountryCode(c.iso2, c.dialCode, c.priority); 180 // area codes 181 if (c.areaCodes) { 182 for (var j = 0; j < c.areaCodes.length; j++) { 183 // full dial code is country code + dial code 184 this._addCountryCode(c.iso2, c.dialCode + c.areaCodes[j]); 185 } 186 } 187 } 188 }, 189 // process preferred countries - iterate through the preferences, fetching the country data for each one 190 _processPreferredCountries: function() { 191 this.preferredCountries = []; 192 for (var i = 0; i < this.options.preferredCountries.length; i++) { 193 var countryCode = this.options.preferredCountries[i].toLowerCase(), countryData = this._getCountryData(countryCode, false, true); 194 if (countryData) { 195 this.preferredCountries.push(countryData); 196 } 197 } 198 }, 199 // generate all of the markup for the plugin: the selected flag overlay, and the dropdown 200 _generateMarkup: function() { 201 // telephone input 202 this.telInput = $(this.element); 203 // prevent autocomplete as there's no safe, cross-browser event we can react to, so it can easily put the plugin in an inconsistent state e.g. the wrong flag selected for the autocompleted number, which on submit c
203ould mean the wrong number is saved (esp in nationalMode) 204 this.telInput.attr("autocomplete", "off"); 205 // containers (mostly for positioning) 206 this.telInput.wrap($("<div>", { 207 "class": "intl-tel-input" 208 })); 209 this.flagsContainer = $("<div>", { 210 "class": "flag-container" 211 }).insertBefore(this.telInput); 212 // currently selected flag (displayed to left of input) 213 var selectedFlag = $("<div>", { 214 // make element focusable and tab naviagable 215 tabindex: "0", 216 "class": "selected-flag" 217 }).appendTo(this.flagsContainer); 218 this.selectedFlagInner = $("<div>", { 219 "class": "iti-flag" 220 }).appendTo(selectedFlag); 221 // CSS triangle 222 $("<div>", { 223 "class": "iti-arrow" 224 }).appendTo(selectedFlag); 225 // country dropdown: preferred countries, then divider, then all countries 226 this.countryList = $("<ul>", { 227 "class": "country-list hide" 228 }); 229 if (this.preferredCountries.length) { 230 this._appendListItems(this.preferredCountries, "preferred"); 231 $("<li>", { 232 "class": "divider" 233 }).appendTo(this.countryList); 234 } 235 this._appendListItems(this.countries, ""); 236 // this is useful in lots of places 237 this.countryListItems = this.countryList.children(".country"); 238 // create dropdownContainer markup 239 if (this.options.dropdownContainer) { 240 this.dropdown = $("<div>", { 241 "class": "intl-tel-input iti-container" 242 }).append(this.countryList); 243 } else { 244 this.countryList.appendTo(this.flagsContainer); 245 } 246 }, 247 // add a country <li> to the countryList <ul> container 248 _appendListItems: function(countries, className) { 249 // we create so many DOM elements, it is faster to build a temp string 250 // and then add everything to the DOM in one go at the end 251 var tmp = ""; 252 // for each country 253 for (var i = 0; i < countries.length; i++) { 254 var c = countries[i]; 255 // open the list item 256 tmp += "<li class='country " + className + "' data-dial-code='" + c.dialCode + "' data-country-code='" + c.iso2 + "'>"; 257 // add the flag 258 tmp += "<div class='flag-box'><div class='iti-flag " + c.iso2 + "'></div></div>"; 259 // and the country name and dial code 260 tmp += "<span class='country-name'>" + c.name + "</span>"; 261 tmp += "<span class='dial-code'>+" + c.dialCode + "</span>"; 262 // close the list item 263 tmp += "</li>"; 264 } 265 this.countryList.append(tmp); 266 }, 267 // set the initial state of the input value and the selected flag by either extracting a dial code from the given number, or using initialCountry 268 _setInitialState: function() { 269 var val = this.telInput.val(); 270 // if we already have a dial code we can go ahead and set the flag, else fall back to default 271 if (this._getDialCode(val)) { 272 this._updateFlagFromNumber(val); 273 } else if (this.options.initialCountry !== "auto") { 274 // see if we should select a flag 275 if (this.options.initialCountry) { 276 this._setFlag(this.options.initialCountry); 277 } else { 278 // no dial code and no initialCountry, so default to first in list 279 this.defaultCountry = this.preferredCountries.length ? this.preferredCountries[0].iso2 : this.countries[0].iso2; 280 if (!val) { 281 this._setFlag(this.defaultCountry); 282 } 283 } 284 // if empty, insert the default dial code (this function will check !nationalMode and !autoHideDialCode) 285 if (!val) { 286 var countryData = this._getCountryData(this.defaultCountry, false, false); 287 this._updateDialCode(countryData.dialCode, false); 288 } 289 } 290 // NOTE: if initialCountry is set to auto, that will be handled separately 291 // format 292 if (val) { 293 // this wont be run after _updateDialCode as that's only called
293if no val 294 this._updateValFromNumber(val); 295 } 296 }, 297 // initialise the main event listeners: input keyup, and click selected flag 298 _initListeners: function() { 299 var that = this; 300 this._initKeyListeners(); 301 // formatAsYouType prevents the change event from firing, so we need to check for changes between focus and blur in order to manually trigger it 302 if (this.options.autoHideDialCode || this.options.formatAsYouType) { 303 this._initFocusListeners(); 304 } 305 // hack for input nested inside label: clicking the selected-flag to open the dropdown would then automatically trigger a 2nd click on the input which would close it again 306 var label = this.telInput.closest("label"); 307 if (label.length) { 308 label.on("click" + this.ns, function(e) { 309 // if the dropdown is closed, then focus the input, else ignore the click 310 if (that.countryList.hasClass("hide")) { 311 that.telInput.focus(); 312 } else { 313 e.preventDefault(); 314 } 315 }); 316 } 317 // toggle country dropdown on click 318 var selectedFlag = this.selectedFlagInner.parent(); 319 selectedFlag.on("click" + this.ns, function(e) { 320 // only intercept this event if we're opening the dropdown 321 // else let it bubble up to the top ("click-off-to-close" listener) 322 // we cannot just stopPropagation as it may be needed to close another instance 323 if (that.countryList.hasClass("hide") && !that.telInput.prop("disabled") && !that.telInput.prop("readonly")) { 324 that._showDropdown(); 325 } 326 }); 327 // open dropdown list if currently focused 328 this.flagsContainer.on("keydown" + that.ns, function(e) { 329 var isDropdownHidden = that.countryList.hasClass("hide"); 330 if (isDropdownHidden && (e.which == keys.UP || e.which == keys.DOWN || e.which == keys.SPACE || e.which == keys.ENTER)) { 331 // prevent form from being submitted if "ENTER" was pressed 332 e.preventDefault(); 333 // prevent event from being handled again by document 334 e.stopPropagation(); 335 that._showDropdown(); 336 } 337 // allow navigation from dropdown to input on TAB 338 if (e.which == keys.TAB) { 339 that._closeDropdown(); 340 } 341 }); 342 }, 343 // init many requests: utils script / geo ip lookup 344 _initRequests: function() { 345 var that = this; 346 // if the user has specified the path to the utils script, fetch it on window.load, else resolve 347 if (this.options.utilsScript) { 348 // if the plugin is being initialised after the window.load event has already been fired 349 if (windowLoaded) { 350 $.fn[pluginName].loadUtils(this.options.utilsScript, this.utilsScriptDeferred); 351 } else { 352 // wait until the load event so we don't block any other requests e.g. the flags image 353 $(window).load(function() { 354 $.fn[pluginName].loadUtils(that.options.utilsScript, that.utilsScriptDeferred); 355 }); 356 } 357 } else { 358 this.utilsScriptDeferred.resolve(); 359 } 360 if (this.options.initialCountry === "auto") { 361 this._loadAutoCountry(); 362 } else { 363 this.autoCountryDeferred.resolve(); 364 } 365 }, 366 // perform the geo ip lookup 367 _loadAutoCountry: function() { 368 var that = this;
369 // check for cookie 370 var cookieAutoCountry = window.Cookies ? Cookies.get("itiAutoCountry") : ""; 371 if (cookieAutoCountry) { 372 $.fn[pluginName].autoCountry = cookieAutoCountry; 373 } 374 // 3 options: 375 // 1) already loaded (we're done) 376 // 2) not already started loading (start) 377 // 3) already started loading (do nothing - just wait for loading callback to fire) 378 if ($.fn[pluginName].autoCountry) { 379 this.handleAutoCountry(); 380 } else if (!$.fn[pluginName].startedLoadingAutoCountry) { 381 // don't do this twice! 382 $.fn[pluginName].startedLoadingAutoCountry = true; 383 if (typeof this.options.geoIpLookup === "function") { 384 this.options.geoIpLookup(function(countryCode) { 385 $.fn[pluginName].autoCountry = countryCode.toLowerCase(); 386 if (window.Cookies) { 387 Cookies.set("itiAutoCountry", $.fn[pluginName].autoCountry, { 388 path: "/" 389 }); 390 } 391 // tell all instances the auto country is ready 392 // TODO: this should just be the current instances 393 // UPDATE: use setTimeout in case their geoIpLookup function calls this callback straight away (e.g. if they have already done the geo ip lookup somewhere else). Using setTimeout means that the current thread of execution will finish before executing this, which allows the plugin to finish initialising. 394 setTimeout(function() { 395 $(".intl-tel-input input").intlTelInput("handleAutoCountry"); 396 }); 397 }); 398 } 399 } 400 }, 401 // initialize any key listeners 402 _initKeyListeners: function() { 403 var that = this; 404 if (this.options.formatAsYouType) { 405 // format number and update flag on keypress 406 // use keypress event as we want to ignore all input except for a select few keys, 407 // but we dont want to ignore the navigation keys like the arrows etc. 408 // NOTE: no point in refactoring this to only bind these listeners on focus/blur because then you would need to have those 2 listeners running the whole time anyway... 409 this.telInput.on("keypress" + this.ns, function(e) { 410 // 32 is space, and after that it's all chars (not meta/nav keys) 411 // this fix is needed for Firefox, which triggers keypress event for some meta/nav keys 412 // Update: also ignore if this is a metaKey e.g. FF and Safari trigger keypress on the v of Ctrl+v 413 // Update: also ignore if ctrlKey (FF on Windows/Ubuntu) 414 // Update: also check that we have utils before we do any formatAsYouType stuff 415 if (e.which >= keys.SPACE && !e.ctrlKey && !e.metaKey && window.intlTelInputUtils && !that.telInput.prop("readonly")) { 416 e.preventDefault(); 417 // allowed keys are just numeric keys and plus 418 // we must allow plus for the case where the user does select-all and then hits plus to start typing a new number. we could refine this logic to first check that the selection contains a plus, but that wont work in old browsers, and I think it's overkill anyway 419 var isAllowedKey = e.which >= keys.ZERO && e.which <= keys.NINE || e.which == keys.PLUS, input = that.telInput[0], noSelection = that.isGoodBrowser && input.selectionStart == input.selectionEnd, max = that.telInput.attr("maxlength"), val = that.telInput.val(), // assumes that if max exists, it is >0 420 isBelowMax = max ? val.length < max : true; 421 // first: ensure we dont go over maxlength. we must do this here to prevent adding digits in the middle of the number 422 // still reformat even if not an allowed key as they could by typing a formatting char, but ignore if there's a selection as doesn't make sense to replace selection with illegal char and then immediately remove it 423 if (isBelowMax && (isAllowedKey || noSelection)) { 424 var newChar = isAllowedKey ? String.fromCharCode(e.which) : null; 425 that._handleInputKey(newChar, true, isAllowedKey
425); 426 // if something has changed, trigger the input event (which was otherwised squashed by the preventDefault) 427 if (val != that.telInput.val()) { 428 that.telInput.trigger("input"); 429 } 430 } 431 if (!isAllowedKey) { 432 that._handleInvalidKey(); 433 } 434 } 435 }); 436 } 437 // handle cut/paste event (now supported in all major browsers) 438 this.telInput.on("cut" + this.ns + " paste" + this.ns, function() { 439 // hack because "paste" event is fired before input is updated 440 setTimeout(function() { 441 if (that.options.formatAsYouType && window.intlTelInputUtils) { 442 var cursorAtEnd = that.isGoodBrowser && that.telInput[0].selectionStart == that.telInput.val().length; 443 that._handleInputKey(null, cursorAtEnd, true); 444 that._ensurePlus(); 445 } else { 446 // if no formatAsYouType, just update flag 447 that._updateFlagFromNumber(that.telInput.val()); 448 } 449 }); 450 }); 451 // handle keyup event 452 // if formatAsYouType enabled: we use keyup to catch delete events (after the fact) 453 // if no formatAsYouType, this is used to update the flag 454 this.telInput.on("keyup" + this.ns, function(e) { 455 // the "enter" key event from selecting a dropdown item is triggered here on the input, because the document.keydown handler that initially handles that event triggers a focus on the input, and so the keyup for that same key event gets triggered here. weird, but just make sure we dont bother doing any re-formatting in this case (we've already done preventDefault in the keydown handler, so it wont actually submit the form or anything). 456 // ALSO: ignore keyup if readonly 457 if (e.which == keys.ENTER || that.telInput.prop("readonly")) {} else if (that.options.formatAsYouType && window.intlTelInputUtils) { 458 // cursorAtEnd defaults to false for bad browsers else they would never get a reformat on delete 459 var cursorAtEnd = that.isGoodBrowser && that.telInput[0].selectionStart == that.telInput.val().length; 460 if (!that.telInput.val()) { 461 // if they just cleared the input, update the flag to the default 462 that._updateFlagFromNumber(""); 463 } else if (e.which == keys.DEL && !cursorAtEnd || e.which == keys.BSPACE) { 464 // if delete in the middle: reformat with no suffix (no need to reformat if delete at end) 465 // if backspace: reformat with no suffix (need to reformat if at end to remove any lingering suffix - this is a feature) 466 // important to remember never to add suffix on any delete key as can confuse ie8 so you can never delete a formatting char at the end 467 that._handleInputKey(null, false, false); 468 } 469 that._ensurePlus(); 470 } else { 471 // if no formatAsYouType, just update flag 472 that._updateFlagFromNumber(that.telInput.val()); 473 } 474 }); 475 }, 476 // if not in nationalMode: prevent deleting the plus and put the cursor back in the right place 477 _ensurePlus: function() { 478 if (!this.options.nationalMode) { 479 var val = this.telInput.val(), input = this.telInput[0]; 480 if (val.charAt(0) != "+") { 481 // newCursorPos is current pos + 1 to account for the plus we are about to add 482 var newCursorPos = this.isGoodBrowser ? input.selectionStart + 1 : 0; 483 this.telInput.val("+" + val); 484 if (this.isGoodBrowser) { 485 input.setSelectionRange(newCursorPos, newCursorPos); 486 } 487 } 488 } 489 },
490 // alert the user to an invalid key event 491 _handleInvalidKey: function() { 492 var that = this; 493 this.telInput.trigger("invalidkey").addClass("iti-invalid-key"); 494 setTimeout(function() { 495 that.telInput.removeClass("iti-invalid-key"); 496 }, 100); 497 }, 498 // when formatAsYouType is enabled: handle various key events on the input: 499 // 1) adding a new number character, which will replace any selection, reformat, and preserve the cursor position 500 // 2) reformatting on backspace/delete 501 // 3) cut/paste event 502 _handleInputKey: function(newNumericChar, addSuffix, isAllowedKey) { 503 var val = this.telInput.val(), cleanBefore = this._getClean(val), originalLeftChars, // raw DOM element 504 input = this.telInput[0], digitsOnRight = 0; 505 if (this.isGoodBrowser) { 506 // cursor strategy: maintain the number of digits on the right. we use the right instead of the left so that A) we dont have to account for the new digit (or multiple digits if paste event), and B) we're always on the right side of formatting suffixes 507 digitsOnRight = this._getDigitsOnRight(val, input.selectionEnd); 508 // if handling a new number character: insert it in the right place 509 if (newNumericChar) { 510 // replace any selection they may have made with the new char 511 val = val.substr(0, input.selectionStart) + newNumericChar + val.substring(input.selectionEnd, val.length); 512 } else { 513 // here we're not handling a new char, we're just doing a re-format (e.g. on delete/backspace/paste, after the fact), but we still need to maintain the cursor position. so make note of the char on the left, and then after the re-format, we'll count in the same number of digits from the right, and then keep going through any formatting chars until we hit the same left char that we had before. 514 // UPDATE: now have to store 2 chars as extensions formatting contains 2 spaces so you need to be able to distinguish 515 originalLeftChars = val.substr(input.selectionStart - 2, 2); 516 } 517 } else if (newNumericChar) { 518 val += newNumericChar; 519 } 520 // ensure starts with plus 521 if (!this.options.nationalMode && val.charAt(0) != "+") { 522 val = "+" + val; 523 } 524 // we must update the flag first, which updates this.selectedCountryData, which is used later for formatting the number before displaying it 525 this._updateFlagFromNumber(val); 526 this._updateValAsYouType(val, addSuffix, isAllowedKey); 527 // update the cursor position 528 if (this.isGoodBrowser) { 529 var newCursor; 530 val = this.telInput.val(); 531 // if it was at the end, keep it there 532 if (!digitsOnRight) { 533 newCursor = val.length; 534 } else { 535 // else count in the same number of digits from the right 536 newCursor = this._getCursorFromDigitsOnRight(val, digitsOnRight); 537 // but if delete/paste etc, keep going left until hit the same left char as before 538 if (!newNumericChar) { 539 newCursor = this._getCursorFromLeftChar(val, newCursor, originalLeftChars); 540 } 541 } 542 // set the new cursor 543 input.setSelectionRange(newCursor, newCursor); 544 } 545 }, 546 // try to formatNumberAsYouType, then update the input val 547 // NOTE: called from _handleInputKey, _updateDialCode (when change flag) and in the focus handler for autoHideDialCode
548 _updateValAsYouType: function(val, addSuffix, isAllowedKey) { 549 var formatted; 550 if (window.intlTelInputUtils && this.selectedCountryData) { 551 formatted = this._cap(intlTelInputUtils.formatNumberAsYouType(val, this.selectedCountryData.iso2, addSuffix, this.options.allowExtensions, isAllowedKey)); 552 } else { 553 formatted = val; 554 } 555 this.telInput.val(formatted); 556 }, 557 // adhere to the input's maxlength attr 558 _cap: function(number) { 559 var max = this.telInput.attr("maxlength"); 560 return max && number.length > max ? number.substr(0, max) : number; 561 }, 562 // we start from the position in guessCursor, and work our way left until we hit the originalLeftChars or a number to make sure that after reformatting the cursor has the same char on the left in the case of a delete etc 563 _getCursorFromLeftChar: function(val, guessCursor, originalLeftChars) { 564 for (var i = guessCursor; i > 0; i--) { 565 var leftChar = val.charAt(i - 1); 566 // UPDATE: now have to store 2 chars as extensions formatting contains 2 spaces so you need to be able to distinguish 567 if ($.isNumeric(leftChar) || val.substr(i - 2, 2) == originalLeftChars) { 568 return i; 569 } 570 } 571 return 0; 572 }, 573 // after a reformat we need to make sure there are still the same number of digits to the right of the cursor 574 _getCursorFromDigitsOnRight: function(val, digitsOnRight) { 575 for (var i = val.length - 1; i >= 0; i--) { 576 if ($.isNumeric(val.charAt(i)) && --digitsOnRight === 0) { 577 return i; 578 } 579 } 580 return 0; 581 }, 582 // get the number of numeric digits to the right of the cursor so we can reposition the cursor correctly after the reformat has happened 583 _getDigitsOnRight: function(val, selectionEnd) { 584 var digitsOnRight = 0; 585 for (var i = selectionEnd; i < val.length; i++) { 586 if ($.isNumeric(val.charAt(i))) { 587 digitsOnRight++; 588 } 589 } 590 return digitsOnRight; 591 }, 592 // listen for focus and blur 593 _initFocusListeners: function() { 594 var that = this; 595 if (this.options.autoHideDialCode) { 596 // mousedown decides where the cursor goes, so if we're focusing we must preventDefault as we'll be inserting the dial code, and we want the cursor to be at the end no matter where they click 597 this.telInput.on("mousedown" + this.ns, function(e) { 598 if (!that.telInput.is(":focus") && !that.telInput.val()) { 599 e.preventDefault(); 600 // but this also cancels the focus, so we must trigger that manually 601 that.telInput.focus(); 602 } 603 }); 604 } 605 this.telInput.on("focus" + this.ns, function(e) { 606 var value = that.telInput.val(); 607 // save this to compare on blur 608 that.telInput.data("focusVal", value); 609 // on focus: if empty, insert the dial code for the currently selected flag 610 if (that.options.autoHideDialCode && !value && !that.telInput.prop("readonly") && that.selectedCountryData.dialCode) { 611 // apply as-you-type-formatting as some dial codes are long enough 612 that._updateValAsYouType("+" + that.selectedCountryData.dialCode, true, false); 613 // after auto-inserting a dial code, if the first key they hit is '+' then assume they are entering a new number, so remove the dial code. use keypress instead of keydown because keydown gets triggered for the shift key (required to hit the + key), and instead of keyup because that shows the new '+' before removing the old one 614 that.telInput.one("keypress.plus" + that.ns, function(e) { 615 if (e.which == keys.PLUS) { 616 // if formatAsYouType is enabled, this key event will have already have been handled by another keypress listener (hence we need to add the "+"). if disabled, it will be handled after this by a keyup listener (hence no need to add the "+"). 617 var newVal = that.options.formatAsYouType && window.intlTelInputUtils ? "+" : ""; 618 that.telInput.val(newVal); 619 } 620 }); 621 // after tabbing in, make sure the cursor is at the end we must use setTimeout to get outside of the focus handler as it seems the selection happens after that 622 setTimeout(function() { 623 var input = that.telInput[0]; 624 if (that.isGoodBrowser) { 625 var len = that.telInput.val().length; 626 input.setSelectionRange(len, len); 627 } 628 }); 629 } 630 }); 631 this.telInput.on("blur" + this.ns, function() { 632 if (that.options.autoHideDialCode) { 633 // on blur: if just a dial code then remove it 634 var value = that.telInput.val(), startsPlus = value.charAt(0) == "+"; 635 if (startsPlus) { 636 var numeric = that._getNumeric(value); 637 // if just a plus, or if just a dial code 638 if (!numeric || that.selectedCountryData.dialCode == numeric) { 639 that.telInput.val(""); 640 } 641 } 642 // remove the keypress listener we added on focus 643 that.telInput.off("keypress.plus" + that.ns); 644 } 645 // if formatAsYouType, we must manually trigger change event if value has changed 646 if (that.options.formatAsYouType && window.intlTelInputUtils && that.telInput.val() != that.telInput.data("focusVal")) { 647 that.telInput.trigger("change"); 648 } 649 }); 650 }, 651 // extract the numeric digits from the given string 652 _getNumeric: function(s) { 653 return s.replace(/\D/g, ""); 654 }, 655 // extract the clean number (numeric with optionally plus) 656 _getClean: function(s) { 657 var prefix = s.charAt(0) == "+" ? "+" : ""; 658 return prefix + this._getNumeric(s); 659 }, 660 // show the dropdown 661 _showDropdown: function() { 662 this._setDropdownPosition(); 663 // update highlighting and scroll to active list item 664 var activeListItem = this.countryList.children(".active"); 665 if (activeListItem.length) {
666 this._highlightListItem(activeListItem); 667 this._scrollTo(activeListItem); 668 } 669 // bind all the dropdown-related listeners: mouseover, click, click-off, keydown 670 this._bindDropdownListeners(); 671 // update the arrow 672 this.selectedFlagInner.children(".iti-arrow").addClass("up"); 673 }, 674 // decide where to position dropdown (depends on position within viewport, and scroll) 675 _setDropdownPosition: function() { 676 var that = this; 677 if (this.options.dropdownContainer) { 678 this.dropdown.appendTo(this.options.dropdownContainer); 679 } 680 // show the menu and grab the dropdown height 681 this.dropdownHeight = this.countryList.removeClass("hide").outerHeight(); 682 if (!this.isMobile) { 683 var pos = this.telInput.offset(), inputTop = pos.top, windowTop = $(window).scrollTop(), // dropdownFitsBelow = (dropdownBottom < windowBottom) 684 dropdownFitsBelow = inputTop + this.telInput.outerHeight() + this.dropdownHeight < windowTop + $(window).height(), dropdownFitsAbove = inputTop - this.dropdownHeight > windowTop; 685 // by default, the dropdown will be below the input. If we want to position it above the input, we add the dropup class. 686 this.countryList.toggleClass("dropup", !dropdownFitsBelow && dropdownFitsAbove); 687 // if dropdownContainer is enabled, calculate postion 688 if (this.options.dropdownContainer) { 689 // by default the dropdown will be directly over the input because it's not in the flow. If we want to position it below, we need to add some extra top value. 690 var extraTop = !dropdownFitsBelow && dropdownFitsAbove ? 0 : this.telInput.innerHeight(); 691 // calculate placement 692 this.dropdown.css({ 693 top: inputTop + extraTop, 694 left: pos.left 695 }); 696 // close menu on window scroll 697 $(window).on("scroll" + this.ns, function() { 698 that._closeDropdown(); 699 }); 700 } 701 } 702 }, 703 // we only bind dropdown listeners when the dropdown is open 704 _bindDropdownListeners: function() { 705 var that = this; 706 // when mouse over a list item, just highlight that one 707 // we add the class "highlight", so if they hit "enter" we know which one to select 708 this.countryList.on("mouseover" + this.ns, ".country", function(e) { 709 that._highlightListItem($(this)); 710 }); 711 // listen for country selection 712 this.countryList.on("click" + this.ns, ".country", function(e) { 713 that._selectListItem($(this)); 714 }); 715 // click off to close 716 // (except when this initial opening click is bubbling up) 717 // we cannot just stopPropagation as it may be needed to close another instance 718 var isOpening = true; 719 $("html").on("click" + this.ns, function(e) { 720 if (!isOpening) { 721 that._closeDropdown(); 722 } 723 isOpening = false; 724 }); 725 // listen for up/down scrolling, enter to select, or letters to jump to country name. 726 // use keydown as keypress doesn't fire for non-char keys and we want to catch if they 727 // just hit down and hold it to scroll down (no keyup event). 728 // listen on the document because that's where key events are triggered if no input has focus 729 var query = "", queryTimer = null; 730 $(document).on("keydown" + this.ns, function(e) { 731 // prevent down key from scrolling the whole page, 732 // and enter key from submitting a form etc 733 e.preventDefault(); 734 if (e.which == keys.UP || e.which == keys.DOWN) { 735 // up and down to navigate 736 that._handleUpDownKey(e.which); 737 } else if (e.which == keys.ENTER) { 738 // enter to select 739 that._handleEnterKey(); 740 } else if (e.which == keys.ESC) { 741 // esc to close 742 that._closeDropdown(); 743 } else if (e.which >
743= keys.A && e.which <= keys.Z || e.which == keys.SPACE) { 744 // upper case letters (note: keyup/keydown only return upper case letters) 745 // jump to countries that start with the query string 746 if (queryTimer) { 747 clearTimeout(queryTimer); 748 } 749 query += String.fromCharCode(e.which); 750 that._searchForCountry(query); 751 // if the timer hits 1 second, reset the query 752 queryTimer = setTimeout(function() { 753 query = ""; 754 }, 1e3); 755 } 756 }); 757 }, 758 // highlight the next/prev item in the list (and ensure it is visible) 759 _handleUpDownKey: function(key) { 760 var current = this.countryList.children(".highlight").first(); 761 var next = key == keys.UP ? current.prev() : current.next(); 762 if (next.length) { 763 // skip the divider 764 if (next.hasClass("divider")) { 765 next = key == keys.UP ? next.prev() : next.next(); 766 } 767 this._highlightListItem(next); 768 this._scrollTo(next); 769 } 770 }, 771 // select the currently highlighted item 772 _handleEnterKey: function() { 773 var currentCountry = this.countryList.children(".highlight").first(); 774 if (currentCountry.length) { 775 this._selectListItem(currentCountry); 776 } 777 }, 778 // find the first list item whose name starts with the query string 779 _searchForCountry: function(query) { 780 for (var i = 0; i < this.countries.length; i++) { 781 if (this._startsWith(this.countries[i].name, query)) { 782 var listItem = this.countryList.children("[data-country-code=" + this.countries[i].iso2 + "]").not(".preferred"); 783 // update highlighting and scroll 784 this._highlightListItem(listItem); 785 this._scrollTo(listItem, true); 786 break; 787 } 788 } 789 }, 790 // check if (uppercase) string a starts with string b 791 _startsWith: function(a, b) { 792 return a.substr(0, b.length).toUpperCase() == b; 793 }, 794 // update the input's value to the given val 795 // NOTE: this is called from _setInitialState, handleUtils and setNumber
796 _updateValFromNumber: function(number) { 797 // if nationalMode and number starts "+", then first we convert it e.g. add a zero if converting a GB number from intl to ntl mode 798 if (window.intlTelInputUtils && this.selectedCountryData && this.options.nationalMode && number.charAt(0) === "+") { 799 number = intlTelInputUtils.formatNumber(number, this.selectedCountryData.iso2, intlTelInputUtils.numberFormat.NATIONAL); 800 } 801 // we always finish by applying as-you-type-formatting because that's what will happen if the user types anything 802 // NOTE: the result of the above formatNumber call will not be pretty if the number was incomplete 803 this._updateValAsYouType(number, false, false); 804 }, 805 // check if need to select a new flag based on the given number 806 _updateFlagFromNumber: function(number) { 807 // if we're in nationalMode and we already have US/Canada selected, make sure the number starts with a +1 so _getDialCode will be able to extract the area code 808 // update: if we dont yet have selectedCountryData, but we're here (trying to update the flag from the number), that means we're initialising the plugin with a number that already has a dial code, so fine to ignore this bit 809 if (number && this.options.nationalMode && this.selectedCountryData && this.selectedCountryData.dialCode == "1" && number.charAt(0) != "+") { 810 if (number.charAt(0) != "1") { 811 number = "1" + number; 812 } 813 number = "+" + number; 814 } 815 // try and extract valid dial code from input 816 var dialCode = this._getDialCode(number), countryCode = null; 817 if (dialCode) { 818 // check if one of the matching countries is already selected 819 var countryCodes = this.countryCodes[this._getNumeric(dialCode)], alreadySelected = this.selectedCountryData && $.inArray(this.selectedCountryData.iso2, countryCodes) != -1; 820 // if a matching country is not already selected (or this is an unknown NANP area code): choose the first in the list 821 if (!alreadySelected || this._isUnknownNanp(number, dialCode)) { 822 // if using onlyCountries option, countryCodes[0] may be empty, so we must find the first non-empty index 823 for (var j = 0; j < countryCodes.length; j++) { 824 if (countryCodes[j]) { 825 countryCode = countryCodes[j]; 826 break; 827 } 828 } 829 } 830 } else if (number.charAt(0) == "+" && this._getNumeric(number).length) { 831 // invalid dial code, so empty 832 // Note: use getNumeric here because the number has not been formatted yet, so could contain bad chars 833 countryCode = ""; 834 } else if (!number || number == "+") { 835 // empty, or just a plus, so default 836 countryCode = this.defaultCountry; 837 } 838 if (countryCode !== null) { 839 this._setFlag(countryCode); 840 } 841 }, 842 // check if the given number contains an unknown area code from the North American Numbering Plan i.e. the only dialCode that could be extracted was +1 (instead of say +1 702) and the actual number's length is >=4 843 _isUnknownNanp: function(number, dialCode) { 844 return dialCode == "+1" && this._getNumeric(number).length >= 4; 845 }, 846 // remove highlighting from other list items and highlight the given item 847 _highlightListItem: function(listItem) { 848 this.countryListItems.removeClass("highlight"); 849 listItem.addClass("highlight"); 850 }, 851 // find the country data for the given country code 852 // the ignoreOnlyCountriesOption is only used during init() while parsing the onlyCountries array 853 _getCountryData: function(countryCode, ignoreOnlyCountriesOption, allowFail) { 854 var countryList = ignoreOnlyCountriesOption ? allCountries : this.countries; 855 for (var i = 0; i < countryList.length; i++) { 856 if (countryList[i].iso2 == countryCode) { 857 return countryList[i]; 858 } 859 } 860 if (allowFail) { 861 return null; 862 } else { 863 throw new Error("No country data for '" + countryCode + "'"); 864 } 865 }, 866 // select the given flag, update the placeholder and the active list item 867 _setFlag: function(countryCode) { 868 // do this first as it will throw an error and stop if c
868ountryCode is invalid 869 this.selectedCountryData = countryCode ? this._getCountryData(countryCode, false, false) : {}; 870 // update the defaultCountry - we only need the iso2 from now on, so just store that 871 if (this.selectedCountryData.iso2) { 872 // can't just make this equal to selectedCountryData as would be a ref to that object 873 this.defaultCountry = this.selectedCountryData.iso2; 874 } 875 this.selectedFlagInner.attr("class", "iti-flag " + countryCode); 876 // update the selected country's title attribute 877 var title = countryCode ? this.selectedCountryData.name + ": +" + this.selectedCountryData.dialCode : "Unknown"; 878 this.selectedFlagInner.parent().attr("title", title); 879 // and the input's placeholder 880 this._updatePlaceholder(); 881 // update the active list item 882 this.countryListItems.removeClass("active"); 883 if (countryCode) { 884 this.countryListItems.find(".iti-flag." + countryCode).first().closest(".country").addClass("active"); 885 } 886 }, 887 // update the input placeholder to an example number from the currently selected country 888 _updatePlaceholder: function() { 889 if (window.intlTelInputUtils && !this.hadInitialPlaceholder && this.options.autoPlaceholder && this.selectedCountryData) { 890 var iso2 = this.selectedCountryData.iso2, numberType = intlTelInputUtils.numberType[this.options.numberType], placeholder = iso2 ? intlTelInputUtils.getExampleNumber(iso2, this.options.nationalMode, numberType) : ""; 891 if (typeof this.options.customPlaceholder === "function") { 892 placeholder = this.options.customPlaceholder(placeholder, this.selectedCountryData); 893 } 894 this.telInput.attr("placeholder", placeholder); 895 } 896 }, 897 // called when the user selects a list item from the dropdown 898 _selectListItem: function(listItem) { 899 // update selected flag and active list item 900 this._setFlag(listItem.attr("data-country-code")); 901 this._closeDropdown(); 902 this._updateDialCode(listItem.attr("data-dial-code"), true); 903 // trigger a custom event as even in nationalMode the state has changed 904 this.telInput.trigger("country-change"); 905 // focus the input 906 this.telInput.focus(); 907 // fix for FF and IE11 (with nationalMode=false i.e. auto inserting dial code), who try to put the cursor at the beginning the first time 908 if (this.isGoodBrowser) { 909 var len = this.telInput.val().length; 910 this.telInput[0].setSelectionRange(len, len); 911 } 912 }, 913 // close the dropdown and unbind any listeners 914 _closeDropdown: function() { 915 this.countryList.addClass("hide"); 916 // update the arrow 917 this.selectedFlagInner.children(".iti-arrow").removeClass("up"); 918 // unbind key events 919 $(document).off(this.ns); 920 // unbind click-off-to-close 921 $("html").off(this.ns); 922 // unbind hover and click listeners 923 this.countryList.off(this.ns); 924 // remove menu from container 925 if (this.options.dropdownContainer) { 926 if (!this.isMobile) { 927 $(window).off("scroll" + this.ns); 928 } 929 this.dropdown.detach(); 930 } 931 }, 932 // check if an element is visible within it's container, else scroll until it is 933 _scrollTo: function(element, middle) { 934 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(), middleOffset = containerHeight / 2 - elementHeight / 2;
935 if (elementTop < containerTop) { 936 // scroll up 937 if (middle) { 938 newScrollTop -= middleOffset; 939 } 940 container.scrollTop(newScrollTop); 941 } else if (elementBottom > containerBottom) { 942 // scroll down 943 if (middle) { 944 newScrollTop += middleOffset; 945 } 946 var heightDifference = containerHeight - elementHeight; 947 container.scrollTop(newScrollTop - heightDifference); 948 } 949 }, 950 // replace any existing dial code with the new one (if not in nationalMode) 951 // also we need to know if we're focusing for a couple of reasons e.g. if so, we want to add any formatting suffix, also if the input is empty and we're not in nationalMode, then we want to insert the dial code 952 _updateDialCode: function(newDialCode, focusing) { 953 var inputVal = this.telInput.val(), newNumber; 954 // save having to pass this every time 955 newDialCode = "+" + newDialCode; 956 if (this.options.nationalMode && inputVal.charAt(0) != "+") { 957 // if nationalMode, we just want to re-format 958 newNumber = inputVal; 959 } else if (inputVal) { 960 // if the previous number contained a valid dial code, replace it 961 // (if more than just a plus character) 962 var prevDialCode = this._getDialCode(inputVal); 963 if (prevDialCode.length > 1) { 964 newNumber = inputVal.replace(prevDialCode, newDialCode); 965 } else { 966 // if the previous number didn't contain a dial code, we should persist it 967 var existingNumber = inputVal.charAt(0) != "+" ? $.trim(inputVal) : ""; 968 newNumber = newDialCode + existingNumber; 969 } 970 } else { 971 newNumber = !this.options.autoHideDialCode || focusing ? newDialCode : ""; 972 } 973 this._updateValAsYouType(newNumber, focusing, false); 974 }, 975 // try and extract a valid international dial code from a full telephone number 976 // Note: returns the raw string inc plus character and any whitespace/dots etc 977 _getDialCode: function(number) { 978 var dialCode = ""; 979 // only interested in international numbers (starting with a plus) 980 if (number.charAt(0) == "+") { 981 var numericChars = ""; 982 // iterate over chars 983 for (var i = 0; i < number.length; i++) { 984 var c = number.charAt(i); 985 // if char is number 986 if ($.isNumeric(c)) { 987 numericChars += c; 988 // if current numericChars make a valid dial code 989 if (this.countryCodes[numericChars]) { 990 // store the actual raw string (useful for matching later) 991 dialCode = number.substr(0, i + 1); 992 } 993 // longest dial code is 4 chars 994 if (numericChars.length == 4) { 995 break; 996 } 997 } 998 } 999 } 1000 return dialCode; 1001 }, 1002 /******************** 1003 * PUBLIC METHODS 1004 ********************/ 1005 // this is called when the geoip call returns 1006 handleAutoCountry: function() { 1007 if (this.options.initialCountry === "auto") { 1008 // we must set this even if there is an initial val in the input: in case the initial val is invalid and they delete it - they should see their auto country 1009 this.defaultCountry = $.fn[pluginName].autoCountry; 1010 // if there's no initial value in the input, then update the flag 1011 if (!this.telInput.val()) { 1012 this.setCountry(this.defaultCountry); 1013 } 1014 this.autoCountryDeferred.resolve(); 1015 } 1016 }, 1017 // remove plugin 1018 destroy: function() { 1019 // make sure the dropdown is closed (and unbind listeners) 1020 this._closeDropdown(); 1021 // key events, and focus/blur events if autoHideDialCode=true 1022 this.telInput.off(this.ns); 1023 // click event to open dropdown 1024 this.selectedFlagInner.parent().off(this.ns); 1025 // label click hack 1026 this.telInput.closest("label").off(this.ns); 1027 // remove markup 1028 var container = this.telInput.parent(); 1029 container.before(this.telInput).remove(); 1030 }, 1031 // extract the phone number extension if present 1032 getExtension: function() { 1033 return this.telInput.val().split(" ext. ")[1] || ""; 1034 }, 1035 // format the number to the given format 1036 getNumber: function(format) { 1037 if (window.intlTelInputUtils) { 1038 return intlTelInputUtils.formatNumber(this.telInput.val(), this.selectedCountryData.iso2, format); 1039 } 1040 return ""; 1041 }, 1042 // get the type of the entered number e.g. landline/mobile 1043 getNumberType: function() { 1044 if (window.intlTelInputUtils) { 1045 return intlTelInputUtils.getNumberType(this.telInput.val(), this.selectedCountryData.iso2); 1046 } 1047 return -99; 1048 }, 1049 // get the country data for the currently selected flag 1050 getSelectedCountryData: function() { 1051 // if this is undefined, the plugin will return it's instance instead, so in that case an empty object makes more sense 1052 return this.selectedCountryData || {}; 1053 }, 1054 // get the validation error 1055 getValidationError: function() { 1056 if (window.intlTelInputUtils) { 1057 return intlTelInputUtils.getValidationError(this.telInput.val(), this.selectedCountryData.iso2); 1058 } 1059 return -99; 1060 }, 1061 // validate the input val - assumes the global function isValidNumber (from utilsScript) 1062 isValidNumber: function() { 1063 var val = $.trim(this.telInput.val()), countryCode = this.options.nationalMode ? this.selectedCountryData.iso2 : ""; 1064 if (window.intlTelInputUtils) { 1065 return intlTelInputUtils.isValidNumber(val, countryCode); 1066 } 1067 return false;
1068 }, 1069 // update the selected flag, and update the input val accordingly () 1070 setCountry: function(countryCode) { 1071 countryCode = countryCode.toLowerCase(); 1072 // check if already selected 1073 if (!this.selectedFlagInner.hasClass(countryCode)) { 1074 this._setFlag(countryCode); 1075 this._updateDialCode(this.selectedCountryData.dialCode, false); 1076 } 1077 }, 1078 // set the input value and update the flag 1079 // NOTE: format arg is for public method: to allow devs to format how they want 1080 setNumber: function(number, format) { 1081 // we must update the flag first, which updates this.selectedCountryData, which is used later for formatting the number before displaying it 1082 this._updateFlagFromNumber(number); 1083 // if they specify a format then do that 1084 if ($.isNumeric(format) && window.intlTelInputUtils && this.selectedCountryData) { 1085 this.telInput.val(this._cap(intlTelInputUtils.formatNumber(val, this.selectedCountryData.iso2, format))); 1086 } else { 1087 this._updateValFromNumber(number); 1088 } 1089 }, 1090 // this is called when the utils request completes 1091 handleUtils: function() { 1092 // if the request was successful 1093 if (window.intlTelInputUtils) { 1094 // if formatAsYouType is enabled and there's an initial value in the input, then format it 1095 if (this.telInput.val()) { 1096 this._updateValFromNumber(this.telInput.val()); 1097 } 1098 this._updatePlaceholder(); 1099 } 1100 this.utilsScriptDeferred.resolve(); 1101 } 1102 }; 1103 // using https://github.com/jquery-boilerplate/jquery-boilerplate/wiki/Extending-jQuery-Boilerplate 1104 // (adapted to allow public functions) 1105 $.fn[pluginName] = function(options) { 1106 var args = arguments; 1107 // Is the first parameter an object (options), or was omitted, 1108 // instantiate a new instance of the plugin. 1109 if (options === undefined || typeof options === "object") { 1110 var deferreds = []; 1111 this.each(function() { 1112 if (!$.data(this, "plugin_" + pluginName)) { 1113 var instance = new Plugin(this, options); 1114 var instanceDeferreds = instance._init(); 1115 // we now have 2 deffereds: 1 for auto country, 1 for utils script 1116 deferreds.push(instanceDeferreds[0]); 1117 deferreds.push(instanceDeferreds[1]); 1118 $.data(this, "plugin_" + pluginName, instance); 1119 } 1120 }); 1121 // return the promise from the "master" deferred object that tracks all the others 1122 return $.when.apply(null, deferreds); 1123 } else if (typeof options === "string" && options[0] !== "_") { 1124 // If the first parameter is a string and it doesn't start 1125 // with an underscore or "contains" the `init`-function, 1126 // treat this as a call to a public method. 1127 // Cache the method call to make it possible to return a value 1128 var returns; 1129 this.each(function() { 1130 var instance = $.data(this, "plugin_" + pluginName); 1131 // Tests that there's already a plugin-instance 1132 // and checks that the requested public method exists 1133 if (instance instanceof Plugin && typeof instance[options] === "function") { 1134 // Call the method of our plugin instance, 1135 // and pass it the supplied arguments. 1136 returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1)); 1137 } 1138 // Allow instances to be destroyed via the 'destroy' method 1139 if (options === "destroy") { 1140 $.data(this, "plugin_" + pluginName, null); 1141 } 1142 }); 1143 // If the earlier cached method gives a value back return the value, 1144 // otherwise return this to preserve chainability. 1145 return returns !== undefined ? returns : this; 1146 } 1147 }; 1148 /******************** 1149 * STATIC METHODS 1150 ********************/ 1151 // get the country data object 1152 $.fn[pluginName].getCountryData = function() { 1153 return allCountries; 1154 }; 1155 // load the utils script 1156 $.fn[pluginName].loadUtils = function(path, utilsScriptDeferred) { 1157 if (!$.fn[pluginName].loadedUtilsScript) { 1158 // don't do this twice! (dont just check if window.intlTelInputUtils exists as if init plugin multiple times in quick succession, it may not have finished loading yet) 1159 $.fn[pluginName].loadedUtilsScript = true; 1160 // dont use $.getScript as it prevents caching 1161 $.ajax({ 1162 url: path, 1163 complete: function() { 1164 // tell all instances that the utils request is complete 1165 $(".intl-tel-input input").intlTelInput("handleUtils"); 1166 }, 1167 dataType: "script", 1168 cache: true 1169 }); 1170 } else if (utilsScriptDeferred) { 1171 utilsScriptDeferred.resolve(); 1172 } 1173 }; 1174 // version 1175 $.fn[pluginName].version = "7.1.1"; 1176 // Tell JSHint to ignore this warning: "character may get silently deleted by one or more browsers"
1177 // jshint -W100 1178 // Array of country objects for the flag dropdown. 1179 // Each contains a name, country code (ISO 3166-1 alpha-2) and dial code. 1180 // Originally from https://github.com/mledoze/countries 1181 // then with a couple of manual re-arrangements to be alphabetical 1182 // then changed Kazakhstan from +76 to +7 1183 // and Vatican City from +379 to +39 (see issue 50) 1184 // and Caribean Netherlands from +5997 to +599 1185 // and Curacao from +5999 to +599 1186 // Removed: Kosovo, Pitcairn Islands, South Georgia 1187 // UPDATE Sept 12th 2015 1188 // List of regions that have iso2 country codes, which I have chosen to omit: 1189 // (based on this information: https://en.wikipedia.org/wiki/List_of_country_calling_codes) 1190 // AQ - Antarctica - all different country codes depending on which "base" 1191 // BV - Bouvet Island - no calling code 1192 // GS - South Georgia and the South Sandwich Islands - "inhospitable collection of islands" - same flag and calling code as Falkland Islands 1193 // HM - Heard Island and McDonald Islands - no calling code 1194 // PN - Pitcairn - tiny population (56), same calling code as New Zealand 1195 // TF - French Southern Territories - no calling code 1196 // UM - United States Minor Outlying Islands - no calling code 1197 // UPDATE the criteria of supported countries or territories (see issue 297) 1198 // Have an iso2 code: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 1199 // Have a country calling code: https://en.wikipedia.org/wiki/List_of_country_calling_codes 1200 // Have a flag 1201 // Must be supported by libphonenumber: https://github.com/googlei18n/libphonenumber 1202 // Update: converted objects to arrays to save bytes! 1203 // Update: added "priority" for countries with the same dialCode as others 1204 // Update: added array of area codes for countries with the same dialCode as others 1205 // So each country array has the following information: 1206 // [ 1207 // Country name, 1208 // iso2 code, 1209 // International dial code, 1210 // Order (if >1 country with same dial code), 1211 // Area codes (if >1 country with same dial code) 1212 // ] 1213 var allCountries = [ [ "Afghanistan (â«Ø§ÙØºØ§ÙØ³ØªØ§Ùâ¬â)", "af", "93" ], [ "Albania (Shqipëri)", "al", "355" ], [ "Algeria (â«Ø§Ùجزائرâ¬â)", "dz", "213" ], [ "American Samoa", "as", "1684" ], [ "Andorra", "ad", "376" ], [ "Angola", "ao", "244" ], [ "Anguilla", "ai", "1264" ], [ "Antigua and Barbuda", "ag", "1268" ], [ "Argentina", "ar", "54" ], [ "Armenia (ÕÕ¡ÕµÕ¡Õ½Õ¿Õ¡Õ¶)", "am", "374" ], [ "Aruba", "aw", "297" ], [ "Australia", "au", "61", 0 ], [ "Austria (Ãsterreich)", "at", "43" ], [ "Azerbaijan (AzÉrbaycan)", "az", "994" ], [ "Bahamas", "bs", "1242" ], [ "Bahrain (â«Ø§ÙØ¨ØØ±ÙÙâ¬â)", "bh", "973" ], [ "Bangladesh (বাà¦à¦²à¦¾à¦¦à§à¦¶)", "bd", "880" ], [ "Barbados", "bb", "1246" ], [ "Belarus (ÐелаÑÑÑÑ)", "by", "375" ], [ "Belgium (België)", "be", "32" ], [ "Belize", "bz", "501" ], [ "Benin (Bénin)", "bj", "229" ], [ "Bermuda", "bm", "1441" ], [ "Bhutan (འà½à¾²à½´à½)", "bt", "975" ], [ "Bolivia", "bo", "591" ], [ "Bosnia and Herzegovina (ÐоÑна и ХеÑÑеговина)", "ba", "387" ], [ "Botswana", "bw", "267" ], [ "Brazil (Brasil)", "br", "55" ], [ "British Indian Ocean Territory", "io", "246" ], [ "British Virgin Islands", "vg", "1284" ], [ "Brunei", "bn", "673" ], [ "Bulgaria (ÐÑлгаÑиÑ)", "bg", "359" ], [ "Burkina Faso", "bf", "226" ], [ "Burundi (Uburundi)", "bi", "257" ], [ "Cambodia (ááááá»áá¶)", "kh", "855" ], [ "Cameroon (Cameroun)", "cm", "237" ], [ "Canada", "ca", "1", 1, [ "204", "226", "236", "249", "250", "289", "306", "343", "365", "387", "403", "416", "418", "431", "437", "438", "450", "506", "514", "519", "548", "579", "581", "587", "604", "613", "639", "647", "672", "705", "709", "742", "778", "780", "782", "807", "819", "825", "867", "873", "902", "905" ] ], [ "Cape Verde (Kabu Verdi)", "cv", "238" ], [ "Caribbean Netherlands", "bq", "599", 1 ], [ "Cayman Islands", "ky", "1345" ], [ "Central African Republic (République centrafricaine)", "cf", "236" ], [ "Chad (Tchad)", "td", "235" ], [ "Chile", "cl", "56" ], [ "China (ä¸å½)", "cn", "86" ], [ "Christmas Island", "cx", "61", 2 ], [ "Cocos (Keeling) Islands", "cc", "61", 1 ], [ "Colombia", "co", "57" ], [ "Comoros (â«Ø¬Ø²Ø± اÙÙ٠رâ¬â)", "km", "269" ], [ "Congo (DRC) (Jamhuri ya Kidemokrasia ya Kongo)", "cd", "243" ], [ "Congo (Republic) (Congo-Brazzaville)", "cg", "242" ], [ "Cook Islands", "ck", "682" ], [ "Costa Rica", "cr", "506" ], [ "Côte dâIvoire", "ci", "225" ], [ "Croatia (Hrvatska)", "hr", "385" ], [ "Cuba", "cu", "53" ], [ "Curaçao", "cw", "599", 0 ], [ "Cyprus (ÎÏÏÏοÏ)", "cy", "357" ], [ "Czech Republic (Äeská republika)", "cz", "420" ], [ "Denmark (Danmark)", "dk", "45" ], [ "Djibouti", "dj", "253" ], [ "Dominica", "dm", "1767" ], [ "Dominican Republic (República Dominicana)", "do", "1", 2, [ "809", "829", "849" ] ], [ "Ecuador", "ec", "593" ], [ "Egypt (â«Ù صرâ¬â)", "eg", "20" ], [ "El Salvador", "sv", "503" ], [ "Equatorial Guinea (Guinea Ecuatorial)", "gq", "240" ], [ "Eritrea", "er", "291" ], [ "Estonia (Eesti)", "ee", "372" ], [ "Ethiopia", "et", "251" ], [ "Falkland Islands (Islas Malvinas)", "fk", "500" ], [ "Faroe Islands (Føroyar)", "fo", "298" ], [ "Fiji", "fj", "679" ], [ "Finland (Suomi)", "fi", "358", 0 ], [ "France", "fr", "33" ], [ "French Guiana (Guyane française)", "gf", "594" ], [ "French Polynesia (Polynésie française)", "pf", "689" ], [ "Gabon", "ga", "241" ], [ "Gambia", "gm", "220" ], [ "Georgia (á¡áá¥áá ááááá)", "ge", "995" ], [ "Germany (Deutschland)", "de", "49" ], [ "Ghana (Gaana)", "gh", "233" ], [ "Gibraltar", "gi", "350" ], [ "Greece (Îλλάδα)", "gr", "30" ], [ "Greenland (Kalaallit Nun
1213aat)", "gl", "299" ], [ "Grenada", "gd", "1473" ], [ "Guadeloupe", "gp", "590", 0 ], [ "Guam", "gu", "1671" ], [ "Guatemala", "gt", "502" ], [ "Guernsey", "gg", "44", 1 ], [ "Guinea (Guinée)", "gn", "224" ], [ "Guinea-Bissau (Guiné Bissau)", "gw", "245" ], [ "Guyana", "gy", "592" ], [ "Haiti", "ht", "509" ], [ "Honduras", "hn", "504" ], [ "Hong Kong (馿¸¯)", "hk", "852" ], [ "Hungary (Magyarország)", "hu", "36" ], [ "Iceland (Ãsland)", "is", "354" ], [ "India (à¤à¤¾à¤°à¤¤)", "in", "91" ], [ "Indonesia", "id", "62" ], [ "Iran (â«Ø§ÛراÙâ¬â)", "ir", "98" ], [ "Iraq (â«Ø§ÙعراÙâ¬â)", "iq", "964" ], [ "Ireland", "ie", "353" ], [ "Isle of Man", "im", "44", 2 ], [ "Israel (â«×שר××â¬â)", "il", "972" ], [ "Italy (Italia)", "it", "39", 0 ], [ "Jamaica", "jm", "1876" ], [ "Japan (æ¥æ¬)", "jp", "81" ], [ "Jersey", "je", "44", 3 ], [ "Jordan (â«Ø§ÙأردÙâ¬â)", "jo", "962" ], [ "Kazakhstan (ÐÐ°Ð·Ð°Ñ ÑÑан)", "kz", "7", 1 ], [ "Kenya", "ke", "254" ], [ "Kiribati", "ki", "686" ], [ "Kuwait (â«Ø§ÙÙÙÙØªâ¬â)", "kw", "965" ], [ "Kyrgyzstan (ÐÑÑгÑзÑÑан)", "kg", "996" ], [ "Laos (ລາວ)", "la", "856" ], [ "Latvia (Latvija)", "lv", "371" ], [ "Lebanon (â«ÙØ¨ÙØ§Ùâ¬â)", "lb", "961" ], [ "Lesotho", "ls", "266" ], [ "Liberia", "lr", "231" ], [ "Libya (â«ÙÙØ¨Ùاâ¬â)", "ly", "218" ], [ "Liechtenstein", "li", "423" ], [ "Lithuania (Lietuva)", "lt", "370" ], [ "Luxembourg", "lu", "352" ], [ "Macau (æ¾³é)", "mo", "853" ], [ "Macedonia (FYROM) (ÐакедониÑа)", "mk", "389" ], [ "Madagascar (Madagasikara)", "mg", "261" ], [ "Malawi", "mw", "265" ], [ "Malaysia", "my", "60" ], [ "Maldives", "mv", "960" ], [ "Mali", "ml", "223" ], [ "Malta", "mt", "356" ], [ "Marshall Islands", "mh", "692" ], [ "Martinique", "mq", "596" ], [ "Mauritania (â«Ù ÙØ±ÙتاÙÙØ§â¬â)", "mr", "222" ], [ "Mauritius (Moris)", "mu", "230" ], [ "Mayotte", "yt", "262", 1 ], [ "Mexico (México)", "mx", "52" ], [ "Micronesia", "fm", "691" ], [ "Moldova (Republica Moldova)", "md", "373" ], [ "Monaco", "mc", "377" ], [ "Mongolia (Ðонгол)", "mn", "976" ], [ "Montenegro (Crna Gora)", "me", "382" ], [ "Montserrat", "ms", "1664" ], [ "Morocco (â«Ø§Ù٠غربâ¬â)", "ma", "212", 0 ], [ "Mozambique (Moçambique)", "mz", "258" ], [ "Myanmar (Burma) (áá¼ááºáá¬)", "mm", "95" ], [ "Namibia (Namibië)", "na", "264" ], [ "Nauru", "nr", "674" ], [ "Nepal (नà¥à¤ªà¤¾à¤²)", "np", "977" ], [ "Netherlands (Nederland)", "nl", "31" ], [ "New Caledonia (Nouvelle-Calédonie)", "nc", "687" ], [ "New Zealand", "nz", "64" ], [ "Nicaragua", "ni", "505" ], [ "Niger (Nijar)", "ne", "227" ], [ "Nigeria", "ng", "234" ], [ "Niue", "nu", "683" ], [ "Norfolk Island", "nf", "672" ], [ "North Korea (ì¡°ì 민주주ì ì¸ë¯¼ ê³µíêµ)", "kp", "850" ], [ "Northern Mariana Islands", "mp", "1670" ], [ "Norway (Norge)", "no", "47", 0 ], [ "Oman (â«Ø¹Ù٠اÙâ¬â)", "om", "968" ], [ "Pakistan (â«Ù¾Ø§Ú©Ø³ØªØ§Ùâ¬â)", "pk", "92" ], [ "Palau", "pw", "680" ], [ "Palestine (â«ÙÙØ³Ø·ÙÙâ¬â)", "ps", "970" ], [ "Panama (Panamá)", "pa", "507" ], [ "Papua New Guinea", "pg", "675" ], [ "Paraguay", "py", "595" ], [ "Peru (Perú)", "pe", "51" ], [ "Philippines", "ph", "63" ], [ "Poland (Polska)", "pl", "48" ], [ "Portugal", "pt", "351" ], [ "Puerto Rico", "pr", "1", 3, [ "787", "939" ] ], [ "Qatar (â«Ùطرâ¬â)", "qa", "974" ], [ "Réunion (La Réunion)", "re", "262", 0 ], [ "Romania (România)", "ro", "40" ], [ "Russia (РоÑÑиÑ)", "ru", "7", 0 ], [ "Rwanda", "rw", "250" ], [ "Saint Barthélemy (Saint-Barthélemy)", "bl", "590", 1 ], [ "Saint Helena", "sh", "290" ], [ "Saint Kitts and Nevis", "kn", "1869" ], [ "Saint Lucia", "lc", "1758" ], [ "Saint Martin (Saint-Martin (partie française))", "mf", "590", 2 ], [ "Saint Pierre and Miquelon (Saint-Pierre-et-Miquelon)", "pm", "508" ], [ "Saint Vincent and the Grenadines", "vc", "1784" ], [ "Samoa", "ws", "685" ], [ "San Marino", "sm", "378" ], [ "São Tomé and PrÃncipe (São Tomé e PrÃncipe)", "st", "239" ], [ "Saudi Arabia (â«Ø§ÙÙ Ù ÙÙØ© Ø§ÙØ¹Ø±Ø¨ÙØ© Ø§ÙØ³Ø¹ÙØ¯ÙØ©â¬â)", "sa", "966" ], [ "Senegal (Sénégal)", "sn", "221" ], [ "Serbia (СÑбиÑа)", "rs", "381" ], [ "Seychelles", "sc", "248" ], [ "Sierra Leone", "sl", "232" ], [ "Singapore", "sg", "65" ], [ "Sint Maarten", "sx", "1721" ], [ "Slovakia (Slovensko)", "sk", "421" ], [ "Slovenia (Slovenija)", "si", "386" ], [ "Solomon Islands", "sb", "677" ], [ "Somalia (Soomaaliya)", "so", "252" ], [ "South Africa", "za", "27" ], [ "South Korea (ëí민êµ)", "kr", "82" ], [ "South Sudan (â«Ø¬ÙÙØ¨ Ø§ÙØ³ÙداÙâ¬â)", "ss", "211" ], [ "Spain (España)", "es", "34" ], [ "Sri Lanka (à·à·âර෠ලà¶à¶à·à·)", "lk", "94" ], [ "Sudan (â«Ø§ÙØ³ÙØ¯Ø§Ùâ¬â)", "sd", "249" ], [ "Suriname", "sr", "597" ], [ "Svalbard and Jan Mayen", "sj", "47", 1 ], [ "Swaziland", "sz", "268" ], [ "Sweden (Sverige)", "se", "46" ], [ "Switzerland (Schweiz)", "ch", "41" ], [ "Syria (â«Ø³ÙØ±ÙØ§â¬â)", "sy", "963" ], [ "Taiwan (å°ç£)", "tw", "886" ], [ "Tajikistan", "tj", "992" ], [ "Tanzania", "tz", "255" ], [ "Thailand (à¹à¸à¸¢)", "th", "66" ], [ "Timor-Leste", "tl", "670" ], [ "Togo", "tg", "228" ], [ "Tokelau", "tk", "690" ], [ "Tonga", "to", "676" ], [ "Trinidad and Tobago", "tt", "1868" ], [ "Tunisia (â«ØªÙÙØ³â¬â)", "tn", "216" ], [ "Turkey (Türkiye)", "tr", "90" ], [ "Turkmenistan", "tm", "993" ], [ "Turks and Caicos Islands", "tc", "1649" ], [ "Tuvalu", "tv", "688" ], [ "U.S. Virgin Islands", "vi", "1340" ], [ "Uganda", "ug", "256" ], [ "Ukraine (УкÑаÑна)", "ua", "380" ], [ "United Arab Emirates (â«Ø§Ùإ٠ارات Ø§ÙØ¹Ø±Ø¨ÙØ© اÙÙ ØªØØ¯Ø©â¬â)", "ae", "971" ], [ "United Kingdom", "gb", "44", 0 ], [ "United States", "us", "1", 0 ], [ "Uruguay", "uy", "598" ], [ "Uzbekistan (OÊ»zbekiston)", "uz", "998" ], [ "Vanuatu", "vu", "678" ], [ "Vatican City (Città del Vaticano)", "va", "39", 1 ], [ "Venezuela", "ve", "58" ], [ "Vietnam (Viá»t Nam)", "vn", "84" ], [ "Wallis and Futuna", "wf", "681" ], [ "Western Sahara (â«Ø§ÙØµØØ±Ø§Ø¡ Ø§ÙØºØ±Ø¨ÙØ©â¬â)", "eh", "212", 1 ], [ "Yemen (â«Ø§ÙÙÙ Ùâ¬â)", "ye", "967" ], [ "Zambia", "zm", "260" ], [ "Zimbabwe", "zw", "263" ], [ "à land Islands", "ax", "358", 1 ] ]; 1214 // loop over all of the countries above 1215 for (var i = 0; i < allCountries.length; i++) { 1216 var c = allCountries[i]; 1217 allCountries[i] = { 1218 name: c[0], 1219 iso2: c[1], 1220 dialCode: c[2], 1221 priority: c[3] || 0, 1222 areaCodes: c[4] || null 1223 }; 1224 } 1225});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.