1;(function() 2{ 3 if (BX.PhoneNumber) 4 return; 5 6 var parserInstance; 7 8 var metadataPromise = null; 9 var metadataLoaded = false; 10 var metadataUrl = '/bitrix/js/main/phonenumber/metadata.json'; 11 var ajaxUrl = '/bitrix/tools/phone_number.php'; 12 13 var metadata = {}; 14 var codeToCountries; 15 16 var MAX_LENGTH_COUNTRY_CODE = 3; // The maximum length of the country calling code. 17 var MIN_LENGTH_FOR_NSN = 2; // The minimum length of the national significant number. 18 var MAX_LENGTH_FOR_NSN = 17; // The ITU says the maximum length should be 15, but one can find longer numbers in Germany. 19 20 /* We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious input from consuming CPU.*/ 21 var MAX_INPUT_STRING_LENGTH = 250; 22 23 var plusChar = '+'; 24 25 /* Digits accepted in phone numbers (ascii, fullwidth, arabic-indic, and eastern arabic digits). */ 26 var validDigits = '0-9'; 27 var dashes = '-'; 28 var slashes = '\/'; 29 var dot = '.'; 30 var whitespace = '\\s'; 31 var brackets = '()\\[\\]'; 32 var tildes = '~'; 33 var extensionSeparators = ';#'; 34 var extensionSymbols = ','; 35 36 var phoneNumberStartPattern = '[' + plusChar + validDigits + ']'; 37 var afterPhoneNumberEndPattern = '[^' + validDigits + extensionSeparators + extensionSymbols + ']+$'; 38 var minLengthPhoneNumberPattern = '[' + validDigits + ']{' + MIN_LENGTH_FOR_NSN + '}'; 39 var validPunctuation = dashes + slashes + dot + whitespace + brackets + tildes + extensionSeparators + extensionSymbols; 40 var significantChars = validDigits + plusChar + extensionSeparators + extensionSymbols; 41 42 var validPhoneNumber = 43 '[' + plusChar + ']{0,1}' + 44 '(?:' + 45 '[' + validPunctuation + ']*' + 46 '[' + validDigits + ']' + 47 '){3,}' + 48 '[' + 49 validPunctuation + 50 validDigits + 51 ']*'; 52 53 var validPhoneNumberPattern = 54 '^(?:'+ 55 // Either a short two-digit-only phone number 56 '^' + minLengthPhoneNumberPattern +'$' + 57 // Or a longer fully parsed phone number (min 3 characters) 58 '|' + '^' + validPhoneNumber + '$' + 59 ')$'; 60 61 var loadMetadata = function() 62 { 63 if(metadataLoaded) 64 { 65 var result = new BX.Promise(); 66 result.fulfill({ 67 codeToCountries: codeToCountries, 68 metadata: metadata 69 }); 70 return result; 71 } 72 else if(metadataPromise) 73 { 74 return metadataPromise; 75 } 76 else 77 { 78 metadataPromise = new BX.Promise(); 79 80 BX.ajax.load({ 81 'url': metadataUrl, 82 'type': 'json', 83 'callback': function(data) 84 { 85 codeToCountries = data.codeToCountries; 86 metadata = data.metadata; 87 data.metadata.forEach(function(metadataRecord) 88 { 89 metadata[metadataRecord['id']] = metadataRecord; 90 }); 91 metadataLoaded = true; 92 metadataPromise.fulfill({ 93 codeToCountries: codeToCountries, 94 metadata: metadata 95 }); 96 } 97 }); 98 return metadataPromise; 99 } 100 }; 101 102 BX.PhoneNumber = function() 103 { 104 this.rawNumber = null; 105 this.country = null; 106 107 this.valid = false; 108 this.countryCode = null; 109 this.nationalNumber = null; 110 this.numberType = null; 111 this.extension = null; 112 this.extensionSeparator = null; 113 114 this.international = false;
115 this.nationalPrefix = null; 116 this.hasPlusChar = false; 117 }; 118 119 BX.PhoneNumber.Format = { 120 'E164': 'E.164', 121 'INTERNATIONAL': 'International', 122 'NATIONAL': 'National' 123 }; 124 125 BX.PhoneNumber.getDefaultCountry = function() 126 { 127 return BX.message('phone_number_default_country'); 128 }; 129 130 BX.PhoneNumber.getUserDefaultCountry = function() 131 { 132 return BX.message('user_default_country'); 133 }; 134 135 BX.PhoneNumber.getIncompleteFormatter = function(defaultCountry) 136 { 137 var result = new BX.Promise(); 138 139 if(metadataLoaded) 140 { 141 result.fulfill(new BX.PhoneNumber.IncompleteFormatter(defaultCountry)); 142 } 143 else 144 { 145 loadMetadata().then(function() 146 { 147 result.fulfill(new BX.PhoneNumber.IncompleteFormatter(defaultCountry)); 148 }); 149 } 150 151 return result; 152 }; 153 154 BX.PhoneNumber.getValidNumberPattern = function() 155 { 156 return validPhoneNumber; 157 }; 158 159 BX.PhoneNumber.getValidNumberRegex = function() 160 { 161 return new RegExp(validPhoneNumber); 162 }; 163 164 BX.PhoneNumber.prototype.format = function(formatType) 165 { 166 if(this.valid) 167 { 168 if(!formatType) 169 { 170 return BX.PhoneNumberFormatter.formatOriginal(this); 171 } 172 else 173 { 174 return BX.PhoneNumberFormatter.format(this, formatType) 175 } 176 } 177 else 178 { 179 if(ShortNumberFormatter.isApplicable(this.getRawNumber())) 180 { 181 return ShortNumberFormatter.format(this.getRawNumber()); 182 } 183 else 184 { 185 return this.rawNumber; 186 } 187 } 188 }; 189 190 BX.PhoneNumber.prototype.getRawNumber = function() 191 { 192 return this.rawNumber; 193 }; 194 195 BX.PhoneNumber.prototype.setRawNumber = function(rawNumber) 196 { 197 this.rawNumber = rawNumber; 198 }; 199 200 BX.PhoneNumber.prototype.getCountry = function() 201 { 202 return this.country; 203 }; 204 205 BX.PhoneNumber.prototype.setCountry = function(country) 206 { 207 this.country = country; 208 }; 209 210 BX.PhoneNumber.prototype.isValid = function() 211 { 212 return this.valid; 213 }; 214 215 BX.PhoneNumber.prototype.setValid = function(valid) 216 { 217 this.valid = valid; 218 }; 219 220 BX.PhoneNumber.prototype.getCountryCode = function() 221 { 222 return this.countryCode; 223 }; 224 225 BX.PhoneNumber.prototype.setCountryCode = function(countryCode) 226 { 227 this.countryCode = countryCode; 228 }; 229 230 BX.PhoneNumber.prototype.getNationalNumber = function() 231 { 232 return this.nationalNumber; 233 }; 234 235 BX.PhoneNumber.prototype.setNationalNumber = function(nationalNumber) 236 { 237 this.nationalNumber = nationalNumber; 238 }; 239 240 BX.PhoneNumber.prototype.getNumberType = function() 241 { 242 return this.numberType; 243 }; 244 245 BX.PhoneNumber.prototype.setNumberType = function(numberType) 246 { 247 this.numberType = numberType; 248 }; 249 250 BX.PhoneNumber.prototype.hasExtension = function() 251 { 252 return !!this.extension; 253 }; 254 255 BX.PhoneNumber.prototype.getExtension = function() 256 { 257 return this.extension; 258 }; 259 260 BX.PhoneNumber.prototype.setExtension = function(extension) 261 { 262 this.extension = extension; 263 }; 264 265 BX.PhoneNumber.prototype.getExtensionSeparator = function() 266 { 267 return this.extensionSeparator; 268 }; 269 270 BX.PhoneNumber.prototype.setExtensionSeparator = function(extensionSeparator) 271 { 272 this.extensionSeparator = extensionSeparator; 273 }; 274 275 BX.PhoneNumber.prototype.isInternational = function() 276 { 277 return this.international; 278 }; 279 280 BX.PhoneNumber.prototype.setInternational = function(international) 281 { 282 this.international = international; 283 }; 284 285 BX.PhoneNumber.prototype.getNationalPrefix = function() 286 { 287 return this.nationalPrefix; 288 }; 289 290 BX.PhoneNumber.prototype.setNationalPrefix = function(nationalPrefix) 291 { 292 this.nationalPrefix = nationalPrefix; 293 }; 294 295 BX.PhoneNumber.prototype.hasPlus = function() 296 { 297 return this.hasPlusChar; 298 }; 299 300 BX.PhoneNumber.prototype.setHasPlus = function(hasPlus) 301 { 302 this.hasPlusChar = hasPlus; 303 }; 304 305 BX.PhoneNumberParser = function() 306 { 307 308 }; 309 310 BX.PhoneNumberParser.getInstance = function() 311 { 312 if(!(parserInstance instanceof BX.PhoneNumberParser)) 313 parserInstance = new BX.PhoneNumberParser(); 314 315 return parserInstance; 316 }; 317 318 BX.PhoneNumberParser.prototype.parse = function(phoneNumber, defaultCountry) 319 { 320 var self = this; 321 var result = new BX.Promise(); 322 323 if(!defaultCountry) 324 defaultCountry = BX.PhoneNumber.getDefaultCountry(); 325 326 if(metadataLoaded) 327 { 328 result.fulfill(self._realParse(phoneNumber, defaultCountry)); 329 } 330 else 331 { 332 loadMetadata().then(function() 333 { 334 result.fulfill(self._realParse(phoneNumber, defaultCountry)); 335 }); 336 } 337 338 return result; 339 }; 340 341 BX.PhoneNumberParser.prototype._realParse = function(phoneNumber, defaultCountry) 342 { 343 var result = new BX.PhoneNumber(); 344 result.setRawNumber(phoneNumber); 345 346 var formattedPhoneNumber = _extractFormattedPhoneNumber(phoneNumber); 347 if(!_isViablePhoneNumber(formattedPhoneNumber)) 348 { 349 return result; 350 } 351 352 var extensionParseResult = _stripExtension(formattedPhoneNumber); 353 var extension = extensionParseResult.extension; 354 var extensionSeparator = extensionParseResult.extensionSeparator; 355 356 formattedPhoneNumber = extensionParseResult.phoneNumber; 357 358 var parseResult = _parsePhoneNumberAndCountryPhoneCode(formattedPhoneNumber); 359 if(parseResult === false) 360 { 361 return result; 362 } 363 364 var country; 365 var countryCode = parseResult['countryCode']; 366 var localNumber = parseResult['localNumber']; 367 var isInternational; 368 var countryMetadata; 369 var hasPlusChar = false;
370 371 if(countryCode) 372 { 373 // Number in international format, thus we ignore $country parameter 374 isInternational = true; 375 hasPlusChar = true; 376 countryMetadata = _getMetadataByCountryCode(countryCode); 377 378 /* 379 $country will be set later, because, for example, for NANPA countries 380 there are several countries corresponding to the same `1` country phone code. 381 Therefore, to reliably determine the exact country, national number should be parsed first. 382 */ 383 country = null; 384 } 385 else if(!defaultCountry) 386 { 387 return result; 388 } 389 else 390 { 391 // Number in national format or in international format without + sign. 392 country = defaultCountry; 393 countryMetadata = _getCountryMetadata(country); 394 if(!countryMetadata) 395 return result; 396 397 countryCode = countryMetadata['countryCode']; 398 var numberWithoutCountryCode = _stripCountryCode(localNumber, countryMetadata); 399 isInternational = (numberWithoutCountryCode !== localNumber); 400 401 localNumber = numberWithoutCountryCode; 402 } 403 404 if(!countryMetadata) 405 { 406 return result; 407 } 408 409 var numberWithoutNationalPrefix = _stripNationalPrefix(localNumber,
409countryMetadata); 410 411 var hadNationalPrefix = false; 412 var nationalPrefix = ''; 413 if (numberWithoutNationalPrefix !== localNumber) 414 { 415 hadNationalPrefix = _isNumberValid(numberWithoutNationalPrefix, countryMetadata); 416 if(hadNationalPrefix) 417 { 418 nationalPrefix = localNumber.substr(0, localNumber.length - numberWithoutNationalPrefix.length); 419 localNumber = numberWithoutNationalPrefix; 420 } 421 } 422 423 // Sometimes there are several countries corresponding to the same country phone code (e.g. NANPA countries all 424 // having `1` country phone code). Therefore, to reliably determine the exact country, national (significant) 425 // number should have been parsed first. 426 if(country === null) 427 { 428 country = _findCountry(countryCode, localNumber); 429 if(!country) 430 { 431 return result; 432 } 433 434 countryMetadata = _getCountryMetadata(country); 435 } 436 437 // Validate local (significant) number length 438 if(localNumber.length > MAX_LENGTH_FOR_NSN) 439 { 440 return result; 441 } 442 443 var nationalNumberRegex = new RegExp('^(?:' + countryMetadata['generalDesc']['nationalNumberPattern'] + ')$'); 444 if(!localNumber.match(nationalNumberRegex)) 445 { 446 return result; 447 } 448
449 var numberType = _getNumberType(localNumber, country); 450 result.setCountry(country); 451 result.setCountryCode(countryCode); 452 result.setNationalNumber(localNumber); 453 result.setNumberType(numberType); 454 result.setInternational(isInternational); 455 result.setHasPlus(hasPlusChar); 456 result.setNationalPrefix(nationalPrefix); 457 result.setExtension(extension); 458 result.setExtensionSeparator(extensionSeparator); 459 result.setValid(numberType !== false); 460 461 return result; 462 }; 463 464 BX.PhoneNumberFormatter = {}; 465 466 BX.PhoneNumberFormatter.format = function(number, formatType) 467 { 468 if(!(number instanceof BX.PhoneNumber)) 469 { 470 throw new Error("number should be instance of BX.PhoneNumber"); 471 } 472 473 if(!metadataLoaded) 474 { 475 throw new Error("Metadata should be loaded prior to calling format"); 476 } 477 478 if(!number.isValid()) 479 return number.getRawNumber(); 480 481 if(formatType === BX.PhoneNumber.Format.E164) 482 { 483 var result = '+' + number.getCountryCode() 484 + number.getNationalNumber() 485 + (number.hasExtension() ? number.getExtensionSeparator() + " " + number.getExtension() : ""); 486 487 return result; 488 } 489 490 var countryMetadata = _getCountryMetadata(number.getCountry()); 491 var isInternational = formatType === BX.PhoneNumber.Format.INTERNATIONAL; 492 var format = this.selectFormatForNumber(number.getNationalNumber(), isInternational, countryMetadata); 493 494 if(format) 495 { 496 var formattedNationalNumber = this.formatNationalNumber( 497 number.getNationalNumber(), 498 isInternational, 499 countryMetadata, 500 format 501 ); 502 } 503 else 504 { 505 formattedNationalNumber = number.getNationalNumber(); 506 } 507 508 if(number.hasExtension()) 509 { 510 formattedNationalNumber += number.getExtensionSeparator() + " " + number.getExtension(); 511 } 512 513 if(formatType === BX.PhoneNumber.Format.INTERNATIONAL) 514 { 515 return '+' + number.getCountryCode() + ' ' + formattedNationalNumber; 516 } 517 else if(formatType === BX.PhoneNumber.Format.NATIONAL) 518 { 519 return formattedNationalNumber; 520 } 521 522 return number.getRawNumber(); 523 }; 524 525 BX.PhoneNumberFormatter.formatOriginal = function(number) 526 { 527 if(!number.isValid()) 528 return number.getRawNumber(); 529 530 var format = this.selectOriginalFormatForNumber(number); 531 if(!format) 532 return number.getRawNumber(); 533 534 var formattedNationalNumber = this.formatNationalNumberWithOriginalFormat(number, format); 535 536 if(number.hasExtension()) 537 { 538 formattedNationalNumber += number.getExtensionSeparator() + " " + number.getExtension(); 539 } 540 541 if(number.isInternational()) 542 { 543 var formattedNumber = (number.hasPlus() ? '+' : '') + number.getCountryCode() + ' ' + formattedNationalNumber; 544 } 545 else 546 { 547 formattedNumber = formattedNationalNumber; 548 } 549 550 // If no digit was inserted/removed/altered in a process of formatting, return the formatted number; 551 var normalizedFormattedNumber = _stripLetters(formattedNumber); 552 var normalizedRawInput = _stripLetters(number.getRawNumber()); 553 if (normalizedFormattedNumber !== normalizedRawInput) 554 { 555 formattedNumber = number.getRawNumber(); 556 } 557 558 return formattedNumber; 559 }; 560 561 BX.PhoneNumberFormatter.selectFormatForNumber = function(nationalNumber, isInternational, countryMetadata) 562 { 563 var availableFormats = _getAvailableFormats(countryMetadata); 564 565 for (var i = 0; i < availableFormats.length; i++) 566 { 567 var format = availableFormats[i]; 568 if(isInternational && format.hasOwnProperty('intlFormat') && format['intlFormat'] === 'NA') 569 continue; 570 571 if(format.hasOwnProperty('leadingDigits') && !_matchLeadingDigits(nationalNumber, format['leadingDigits'])) 572 { 573 continue; 574 } 575 576 var formatPatternRegex = new RegExp('^' + format['pattern'] + '$'); 577 if(nationalNumber.match(formatPatternRegex)) 578 { 579 return format; 580 } 581 } 582 return false; 583 }; 584 585 BX.PhoneNumberFormatter.selectOriginalFormatForNumber = function(number) 586 { 587 var nationalNumber = number.getNationalNumber(); 588 var isInternational = number.isInternational(); 589 var hasNationalPrefix = number.getNationalPrefix() != ''; 590 var countryMetadata = _getCountryMetadata(number.getCountry()); 591 var availableFormats = _getAvailableFormats(countryMetadata); 592 593 for (var i = 0; i < availableFormats.length; i++) 594 { 595 var format = availableFormats[i]; 596 if(isInternational) 597 { 598 if(format.hasOwnProperty('intlFormat') && format['intlFormat'] === 'NA') 599 { 600 continue; 601 } 602 } 603 else 604 {
605 if(hasNationalPrefix && !_isNationalPrefixSupported(format, countryMetadata)) 606 { 607 continue; 608 } 609 } 610 611 612 if(format.hasOwnProperty('leadingDigits') && !_matchLeadingDigits(nationalNumber, format['leadingDigits'])) 613 { 614 continue; 615 } 616 617 var formatPatternRegex = new RegExp('^' + format['pattern'] + '$'); 618 if(nationalNumber.match(formatPatternRegex)) 619 { 620 return format; 621 } 622 } 623 return false; 624 } 625 626 BX.PhoneNumberFormatter.formatNationalNumber = function(nationalNumber, isInternational, countryMetadata, format) 627 { 628 var replaceFormat = (format.hasOwnProperty('intlFormat') && isInternational) ? format['intlFormat'] : format['format']; 629 var patternRegex = new RegExp(format['pattern']); 630 631 if(!isInternational) 632 { 633 var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, countryMetadata); 634 if(nationalPrefixFormattingRule != '') 635 { 636 nationalPrefixFormattingRule = nationalPrefixFormattingRule.replace('$NP', countryMetadata['nationalPrefix']).replace('$FG', '$1'); 637 replaceFormat = replaceFormat.replace(new RegExp('(\\$\\d)'), nationalPrefixFormattingRule); 638 } 639 else 640 { 641 replaceFormat = countryMetadata['nationalPrefix'] + ' ' + replaceFormat; 642 } 643 } 644 645 return nationalNumber.replace(patternRegex, replaceFormat); 646 }; 647 648 BX.PhoneNumberFormatter.formatNationalNumberWithOriginalFormat = function(number, format) 649 { 650 var isInternational = number.isInternational(); 651 var replaceFormat = (format.hasOwnProperty('intlFormat') && isInternational) ? format['intlFormat'] : format['format']; 652 var patternRegex = new RegExp(format['pattern']); 653 var nationalNumber = number.getNationalNumber(); 654 var countryMetadata = _getCountryMetadata(number.getCountry()); 655 var nationalPrefix = _getNationalPrefix(countryMetadata, true); 656 var hasNationalPrefix = _numberContainsNationalPrefix(number.getRawNumber(), nationalPrefix, countryMetadata); 657 658 if(!isInternational && hasNationalPrefix) 659 { 660 var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, countryMetadata); 661 if(nationalPrefixFormattingRule != '') 662 { 663 nationalPrefixFormattingRule = nationalPrefixFormattingRule.replace('$NP', nationalPrefix).replace('$FG', '$1'); 664 replaceFormat = replaceFormat.replace(new RegExp('(\\$\\d)'), nationalPrefixFormattingRule); 665 } 666 else 667 { 668 replaceFormat = nationalPrefix + ' ' + replaceFormat; 669 } 670 } 671 672 return nationalNumber.replace(patternRegex, replaceFormat); 673 }; 674 675 BX.PhoneNumberFormatter.getNationalPrefixFormattingRule = function (countryMetadata, format) 676 { 677 var result = _getNationalPrefixFormattingRule(format, countryMetadata); 678 679 return result.replace('$NP', countryMetadata['nationalPrefix']).replace('$FG', '$1'); 680 }; 681 682 BX.PhoneNumberFormatter.getNationalPrefixOptional = function(countryMetadata, format) 683 { 684 if(BX.type.isPlainObject(format) && format.hasOwnProperty('nationalPrefixOptionalWhenFormatting')) 685 return format['nationalPrefixOptionalWhenFormatting']; 686 else if(countryMetadata.hasOwnProperty('nationalPrefixOptionalWhenFormatting')) 687 return countryMetadata['nationalPrefixOptionalWhenFormatting']; 688 else 689 return false; 690 }; 691 692 693 /* Partial number formatter (used in phone input control */ 694 695 var DUMMY_DIGIT = '9'; 696 var DUMMY_DIGIT_MATCHER = new RegExp(DUMMY_DIGIT, 'g'); 697 var LONGEST_NATIONAL_PHONE_NUMBER_LENGTH = 15; 698 var LONGEST_DUMMY_PHONE_NUMBER = _repeat(DUMMY_DIGIT, LONGEST_NATIONAL_PHONE_NUMBER_LENGTH); 699 var DIGIT_PLACEHOLDER = 'x'; 700 var DIGIT_PLACEHOLDER_MATCHER = new RegExp(DIGIT_PLACEHOLDER); 701 var DIGIT_PLACEHOLDER_MATCHER_GLOBAL = new RegExp(DIGIT_PLACEHOLDER, 'g'); 702 var CHARACTER_CLASS_PATTERN = new RegExp('\\[([^\\[\\]])*\\]', 'g'); //matches character class in some other regexp 703 704 // Any digit in a regular expression that actually denotes a digit. For 705 // example, in the regular expression "80[0-2]\d{6,10}", the first 2 digits 706 // (8 and 0) are standalone digits, but the rest are not. 707 // Two look-aheads are needed because the number following \\d could be a 708 // two-digit number, since the phone number can be as long as 15 digits. 709 var STANDALONE_DIGIT_PATTERN = new RegExp('\\d(?=[^,}][^,}])', 'g'); 710 711 // A pattern that is used to determine if a `format` is eligible 712 // to be used by the "as you type formatter". 713 // It is eligible when the `format` contains groups of the dollar sign 714 // followed by a single digit, separated by valid phone number punctuation. 715 // This prevents invalid punctuation (such as the star sign in Israeli star numbers) 716 // getting into the output of the "as you type formatter". 717 var ELIGIBLE_FORMAT_MATCHER = new RegExp('^' + '[' + validPunctuation + ']*' + '(\\$\\d[' + validPunctuation + ']*)+' + '$'); 718 719 // This is the minimum length of the leading digits of a phone number 720 // to guarantee the first "leading digits pattern" for a phone number format 721 // to be preemptive. 722 var MIN_LEADING_DIGITS_LENGTH = 3; 723 724 var VALID_INCOMPLETE_PHONE_NUMBER = '[' + plusChar + ']{0,1}' + '[' + validPunctuation + validDigits + ']*'; 725 var VALID_INCOMPLETE_PHONE_NUMBER_PATTERN = new RegExp('^' + VALID_INCOMPLETE_PHONE_NUMBER + '$', 'i'); 726 727 BX.PhoneNumber.IncompleteFormatter = function(defaultCountry) 728 { 729 if(!metadataLoaded) 730 { 731 throw new Error("Metadata is not loaded yet. Do not construct this class directly, use BX.PhoneNumber.getIncompleteFormatter instead"); 732 } 733 734 this.defaultCountry = defaultCountry || BX.PhoneNumber.getDefaultCountry(); 735 736 this.rawInput = ''; 737 738 this.country = ''; 739 this.countryCode = ''; 740 this.countryMetadata = null; 741 this.nationalPrefix = ''; 742 this.nationalNumber = ''; 743 this.isInternational = false;
744 this.hasNationalPrefix = false; 745 this.hasPlusChar = false; 746 this.formattedNumber = null; 747 this.extension = ''; 748 this.extensionSeparator = ''; 749 }; 750 751 BX.PhoneNumber.IncompleteFormatter.prototype.format = function(incompleteNumber) 752 { 753 this.resetState(); 754 755 var extractedNumber = _extractFormattedPhoneNumber(incompleteNumber); 756 757 if(!extractedNumber && incompleteNumber[0] === plusChar) 758 { 759 this.rawInput = incompleteNumber; 760 this.formattedNumber = incompleteNumber; 761 return incompleteNumber; 762 } 763 764 this.isInternational = extractedNumber[0] === plusChar; 765 766 var stripResult = _stripExtension(extractedNumber); 767 extractedNumber = stripResult.phoneNumber; 768 this.extension = stripResult.extension; 769 this.extensionSeparator = stripResult.extensionSeparator; 770 771 extractedNumber = _stripLetters(extractedNumber); 772 this.rawInput = extractedNumber; 773 if(this.isInternational) 774 { 775 this.hasPlusChar = true; 776 this.rawInput = plusChar + extractedNumber; 777 } 778 779 if(this.isInternational) 780 { 781 this.extractCountryCode(); 782 if(!this.countryCode) 783 { 784 return this.rawInput; 785 } 786 787 this.findSuitableCountry(); 788 } 789 else if(!this.defaultCountry) 790 { 791 return this.rawInput; 792 } 793 else 794 { 795 this.country = this.defaultCountry; 796 this.countryMetadata = _getCountryMetadata(this.country); 797 if(!this.countryMetadata) 798 { 799 return this.rawInput; 800 } 801 this.nationalNumber = this.rawInput; 802 this.extractNationalPrefix(); 803 804 if(!this.hasNationalPrefix) 805 { 806 this.tryToStripCountryCode(); 807 } 808 } 809 810 return this.getFormattedNumber(); 811 }; 812 813 BX.PhoneNumber.IncompleteFormatter.prototype.getFormattedNumber = function() 814 { 815 var formattedNationalNumber = this.formatNationalNumber(); 816 var result = formattedNationalNumber ? formattedNationalNumber : this.rawInput; 817 818 if(this.extensionSeparator) 819 { 820 result += this.extensionSeparator + " " + this.extension; 821 } 822 823 return result; 824 }; 825 826 BX.PhoneNumber.IncompleteFormatter.prototype.extractCountryCode = function() 827 { 828 var parseResult = _parsePhoneNumberAndCountryPhoneCode(this.rawInput); 829 if(parseResult && parseResult['countryCode']) 830 { 831 this.countryCode = parseResult['countryCode']; 832 this.nationalNumber = parseResult['localNumber']; 833 } 834 }; 835 836 BX.PhoneNumber.IncompleteFormatter.prototype.tryToStripCountryCode = function() 837 { 838 var possibleCountryCode = this.countryMetadata['countryCode']; 839 var possibleNationalNumber; 840 if(this.nationalNumber.indexOf(possibleCountryCode) === 0) 841 { 842 possibleNationalNumber = this.nationalNumber.substr(possibleCountryCode.length); 843 if(_isNumberPossible(possibleNationalNumber, this.countryMetadata, true, false)) 844 { 845 this.isInternational = true; 846 this.countryCode = possibleCountryCode; 847 this.nationalNumber = possibleNationalNumber; 848 } 849 } 850 }; 851 852 BX.PhoneNumber.IncompleteFormatter.prototype.extractNationalPrefix = function() 853 { 854 var possibleNationalNumber = _stripNationalPrefix(this.nationalNumber, this.countryMetadata); 855 856 if(possibleNationalNumber !== this.nationalNumber) 857 { 858 if(!_isNumberPossible(possibleNationalNumber, this.countryMetadata, false, true)) 859 { 860 return false; 861 } 862 this.hasNationalPrefix = true; 863 //this.nationalPrefix = this.nationalNumber.substr(0, this.nationalNumber.length - possibleNationalNumber.length); 864 this.nationalPrefix = this.countryMetadata['nationalPrefix']; 865 this.nationalNumber = possibleNationalNumber; 866 return true; 867 } 868 return false; 869 }; 870 871 BX.PhoneNumber.IncompleteFormatter.prototype.resetState = function() 872 { 873 this.country = null; 874 this.countryCode = ''; 875 this.nationalPrefix = ''; 876 this.nationalNumber = null; 877 this.isInternational = false;
878 this.hasNationalPrefix = false; 879 this.hasPlusChar = false; 880 this.selectedFormat = null; 881 this.formattedNumber = null; 882 this.formattingTemplate = null; 883 this.extension = ''; 884 this.extensionSeparator = ''; 885 }; 886 887 BX.PhoneNumber.IncompleteFormatter.prototype.findSuitableCountry = function() 888 { 889 var possibleCountry = _findCountry(this.countryCode, this.nationalNumber); 890 891 if(possibleCountry) 892 this.country = possibleCountry; 893 else 894 this.country = _getMainCountryForCode(this.countryCode); 895 896 this.countryMetadata = _getCountryMetadata(this.country); 897 }; 898 899 BX.PhoneNumber.IncompleteFormatter.prototype.formatNationalNumber = function() 900 { 901 if(this.isCompleteNumber()) 902 { 903 return this.formatCompleteNumber(this.nationalNumber); 904 } 905 906 if(!this.isInternational && this.countryCode === '' && this.nationalPrefix === '' && ShortNumberFormatter.isApplicable(this.rawInput)) 907 { 908 return ShortNumberFormatter.format(this.rawInput); 909 } 910 911 if(this.selectFormat()) 912 { 913 this.formattedNumber = this.formatUsingTemplate(); 914 915 if(this.isInternational) 916 { 917 var formattedNumber = (this.hasPlusChar ? plusChar : '') + this.countryCode + ' ' + this.formattedNumber; 918 } 919 else 920 { 921 formattedNumber = this.formattedNumber; 922 } 923 924 // If no digit was inserted/removed/altered in a process of formatting, return the formatted number; 925 var normalizedFormattedNumber = _stripLetters(formattedNumber); 926 var normalizedRawInput = _stripLetters(this.rawInput); 927 if (normalizedFormattedNumber !== normalizedRawInput) 928 { 929 formattedNumber = this.rawInput; 930 } 931 932 return formattedNumber; 933 } 934 }; 935 936 BX.PhoneNumber.IncompleteFormatter.prototype.isCompleteNumber = function() 937 { 938 return _getNumberType(this.nationalNumber, this.country) ? true : false; 939 }; 940 941 /** 942 * Take first format with suitable leading digits 943 * @returns boolean 944 */ 945 BX.PhoneNumber.IncompleteFormatter.prototype.selectFormat = function() 946 { 947 var availableFormats = _getAvailableFormats(this.countryMetadata); 948 949 for (var i = 0; i < availableFormats.length; i++) 950 { 951 var format = availableFormats[i]; 952 953 if(!this.isFormatSuitable(format)) 954 continue; 955 956 if(format.hasOwnProperty('leadingDigits') && !_matchLeadingDigits(this.nationalNumber, format['leadingDigits'])) 957 continue; 958 959 if(!this.createFormattingTemplate(format)) 960 continue; 961 962 this.selectedFormat = format; 963 return true; 964 965 } 966 return false; 967 968 }; 969 970 BX.PhoneNumber.IncompleteFormatter.prototype.createFormattingTemplate = function(format) 971 { 972 var pattern = format['pattern']; 973 974 // The IncompleteFormatter doesn't format numbers when pattern contains "|", e.g. (20|3)\d{4}. 975 if(pattern.indexOf('|') !== -1) 976 return false; 977 978 this.formattingTemplate = ""; 979 var possibleTemplate = this.getFormattingTemplate(pattern, format); 980 if(possibleTemplate) 981 { 982 this.formattingTemplate = possibleTemplate; 983 return true; 984 } 985 return false; 986 }; 987 988 BX.PhoneNumber.IncompleteFormatter.prototype.getFormattingTemplate = function(numberPattern, format) 989 { 990 var numberFormat = _getFormatFormat(format, this.isInternational); 991 992 // replace anything in the form of [..] with \d 993 var modifiedPattern = numberPattern.replace(CHARACTER_CLASS_PATTERN, "\\d"); 994 995 // Replace any standalone digit (not the one in d{}) with \d 996 modifiedPattern = modifiedPattern.replace(STANDALONE_DIGIT_PATTERN, "\\d"); 997 998 var longestNumberForPattern = LONGEST_DUMMY_PHONE_NUMBER.match(new RegExp(modifiedPattern))[0]; 999 1000 // formatting template can not be applied right now if current number length > longest number length 1001 1002 if(this.nationalNumber.length > longestNumberForPattern.length) 1003 return false; 1004 1005 if(this.hasNationalPrefix) 1006 { 1007 var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, this.countryMetadata); 1008 if(nationalPrefixFormattingRule) 1009 { 1010 nationalPrefixFormattingRule = nationalPrefixFormattingRule.replace('$NP', this.nationalPrefix).replace('$FG', '$1'); 1011 numberFormat = numberFormat.replace(new RegExp('(\\$\\d)'), nationalPrefixFormattingRule); 1012 } 1013 else 1014 { 1015 numberFormat = this.nationalPrefix + ' ' + numberFormat; 1016 } 1017 } 1018 1019 // format the longest number according to the numberFormat 1020 var template = longestNumberForPattern.replace(new RegExp(modifiedPattern, 'g'), numberFormat); 1021 // replace each digit with the placeholder 1022 template = template.replace(DUMMY_DIGIT_MATCHER, DIGIT_PLACEHOLDER); 1023 return template; 1024 }; 1025 1026 BX.PhoneNumber.IncompleteFormatter.prototype.formatUsingTemplate = function() 1027 { 1028 if(!this.formattingTemplate) 1029 return false;
1030 1031 var result = this.formattingTemplate; 1032 var lastMatchPosition; 1033 1034 for(var i = 0; i< this.nationalNumber.length; i++) 1035 { 1036 lastMatchPosition = result.search(DIGIT_PLACEHOLDER_MATCHER); 1037 if(lastMatchPosition === -1) 1038 return false; 1039 1040 result = result.replace(DIGIT_PLACEHOLDER_MATCHER, this.nationalNumber[i]); 1041 } 1042 1043 result = this.closeLastBracket(result, lastMatchPosition + 1); 1044 return result; 1045 }; 1046 1047 BX.PhoneNumber.IncompleteFormatter.prototype.closeLastBracket = function(partiallyPopulatedTemplate, cutAfter) 1048 { 1049 var remainingTemplatePart = partiallyPopulatedTemplate.substr(cutAfter); 1050 1051 var openingBracketPosition = remainingTemplatePart.indexOf('('); 1052 var closingBracketPosition = remainingTemplatePart.indexOf(')'); 1053 1054 if(closingBracketPosition !== -1 && (openingBracketPosition === -1 || openingBracketPosition > closingBracketPosition)) 1055 { 1056 cutAfter = cutAfter + closingBracketPosition + 1; 1057 } 1058 1059 1060 return partiallyPopulatedTemplate.substr(0, cutAfter).replace(DIGIT_PLACEHOLDER_MATCHER_GLOBAL, ' '); 1061 }; 1062 1063 BX.PhoneNumber.IncompleteFormatter.prototype.formatCompleteNumber = function() 1064 { 1065 var phoneNumber = new BX.PhoneNumber(); 1066 phoneNumber.setRawNumber(this.rawInput); 1067 phoneNumber.setHasPlus(this.hasPlusChar); 1068 phoneNumber.setInternational(this.isInternational); 1069 phoneNumber.setNationalPrefix(this.nationalPrefix); 1070 phoneNumber.setNationalNumber(this.nationalNumber); 1071 phoneNumber.setCountry(this.country); 1072 phoneNumber.setCountryCode(this.countryCode); 1073 1074 var format = BX.PhoneNumberFormatter.selectOriginalFormatForNumber(phoneNumber); 1075 1076 if(!format) 1077 return false; 1078 1079 var formattedNumber = BX.PhoneNumberFormatter.formatNationalNumberWithOriginalFormat(phoneNumber, format); 1080 1081 if(this.isInternational) 1082 { 1083 formattedNumber = (this.hasPlusChar ? plusChar : '') + this.countryCode + ' ' + formattedNumber; 1084 } 1085 1086 this.selectedFormat = format; 1087 return formattedNumber; 1088 }; 1089 1090 BX.PhoneNumber.IncompleteFormatter.prototype.isFormatSuitable = function(format) 1091 { 1092 if(this.isInternational) 1093 { 1094 return _getInternationalFormat(format) ? true : false; 1095 } 1096 else 1097 { 1098 return !this.hasNationalPrefix || _isNationalPrefixSupported(format, this.countryMetadata); 1099 } 1100 }; 1101 1102 BX.PhoneNumber.IncompleteFormatter.prototype.replaceCountry = function (country) 1103 { 1104 this.isInternational = true; 1105 this.hasPlusChar = true; 1106 this.country = country; 1107 this.countryMetadata = _getCountryMetadata(this.country); 1108 this.countryCode = this.countryMetadata['countryCode']; 1109 this.rawInput = '+' + this.countryCode + this.nationalNumber; 1110 this.nationalPrefix = ''; 1111 }; 1112 1113 /** 1114 * 1115 * @param {object} params 1116 * @param {Element} params.node 1117 * @param {Element} [params.flagNode] 1118 * @param {int} [params.flagSize] 16, 24 or 32 1119 * @param {string} [params.defaultCountry] 1120 * @param {string} [params.userDefaultCountry] 1121 * @param {string} [params.savedCountryCode] 1122 * @param {int} [params.countryPopupHeight] 180 1123 * @param {string} [params.countryPopupClassName] '' 1124 * @param {Array} [params.countryTopList] 1125 * @param {boolean} [params.forceLeadingPlus] 1126 * @param {Function} [params.onInitialize] 1127 * @param {Function} [params.onChange] 1128 * @param {Function} [params.onCountryChange] 1129 * @constructor 1130 */ 1131 BX.PhoneNumber.Input = function(params) 1132 { 1133 if(!BX.type.isDomNode(params.node) || params.node.nodeName !== 'INPUT' || params.node.type !== 'text') 1134 { 1135 throw new Error("params.node should be text input node"); 1136 } 1137 1138 this.inputNode = params.node; 1139 this.defaultCountry = params.defaultCountry || BX.PhoneNumber.getDefaultCountry(); 1140 this.userDefaultCountry = params.userDefaultCountry || BX.PhoneNumber.getUserDefaultCountry(); 1141 this.savedCountryCode = params.savedCountryCode || ''; 1142 this.forceLeadingPlus = params.forceLeadingPlus === true; 1143 this.flagNode = BX.type.isDomNode(params.flagNode) ? params.flagNode : null; 1144 this.flagSize = ([16, 24, 32].indexOf(params.flagSize) !== -1) ? params.flagSize : 16; 1145 this.countryPopupHeight = params.countryPopupHeight || 180; 1146 this.countryPopupClassName = params.countryPopupClassName || ''; 1147 this.countryTopList = params.countryTopList || []; 1148 this.flagNodeInitialClass = ''; 1149 1150 this.countries = null; 1151 1152 this.callbacks = { 1153 initialize: BX.type.isFunction(params.onInitialize) ? params.onInitialize : BX.DoNothing, 1154 change: BX.type.isFunction(params.onChange) ? params.onChange : BX.DoNothing, 1155 countryChange: BX.type.isFunction(params.onCountryChange) ? params.onCountryChange : BX.DoNothing 1156 }; 1157 1158 this.formatter = null; 1159 this.countrySelectPopup = null; 1160 1161 this._lastCaretPosition = null; 1162 this._digitsToTheLeft = 0; 1163 this._digitsToTheRight = 0; 1164 this._digitsCount = 0; 1165 this._selectedDigitsBeforeAction = 0; 1166 this._countryBefore = ''; 1167 1168 this.initialized = false;
1169 this.initializationPromises = []; 1170 1171 this.init(); 1172 this.bindEvents(); 1173 }; 1174 1175 BX.PhoneNumber.Input.prototype.init = function() 1176 { 1177 var self = this; 1178 1179 if(this.flagNode) 1180 { 1181 this.flagNodeInitialClass = this.flagNode.className; 1182 BX.adjust(this.flagNode, {style: { 1183 cursor: "pointer", 1184 display: "inline-block" 1185 }}); 1186 } 1187 1188 BX.PhoneNumber.getIncompleteFormatter(this.defaultCountry).then(function(formatter) 1189 { 1190 self.formatter = formatter; 1191 1192 if(self.inputNode.value) 1193 { 1194 self.inputNode.value = self.formatter.format(self.inputNode.value); 1195 1196 if ( 1197 BX.Type.isStringFilled(self.savedCountryCode) 1198 && self.formatter.country !== self.savedCountryCode 1199 ) 1200 { 1201 self.formatter.replaceCountry(self.savedCountryCode); 1202 } 1203 } 1204 else if(self.userDefaultCountry != '') 1205 { 1206 self.formatter.replaceCountry(self.userDefaultCountry); 1207 self.inputNode.value = self.userDefaultCountry === 'XX' ? '' : self.formatter.getFormattedNumber(); 1208 } 1209 self.drawCountryFlag(); 1210 self.initialized = true; 1211 self.initializationPromises.forEach(function(promise) 1212 { 1213 promise.resolve(); 1214 }); 1215 self.callbacks.initialize(); 1216 }); 1217 }; 1218 1219 BX.PhoneNumber.Input.prototype.bindEvents = function () 1220 { 1221 this.inputNode.addEventListener('keydown', this._onKeyDown.bind(this)); 1222 this.inputNode.addEventListener('input', this._onInput.bind(this)); 1223 if(this.flagNode) 1224 { 1225 this.flagNode.addEventListener('click', this._onFlagClick.bind(this)); 1226 } 1227 }; 1228 1229 BX.PhoneNumber.Input.prototype.setValue = function (newValue) 1230 { 1231 this.waitForInitialization().then(function() 1232 { 1233 this.inputNode.value = this.formatter.format(newValue.toString()); 1234 this.callbacks.change({ 1235 value: this.getValue(), 1236 formattedValue: this.getFormattedValue(), 1237 country: this.getCountry(), 1238 countryCode: this.getCountryCode() 1239 }); 1240 1241 if(this._countryBefore !== this.getCountry()) 1242 { 1243 this.drawCountryFlag(); 1244 this.callbacks.countryChange({ 1245 country: this.getCountry(), 1246 countryCode: this.getCountryCode() 1247 }); 1248 } 1249 }.bind(this)); 1250 }; 1251 1252 BX.PhoneNumber.Input.prototype.waitForInitialization = function() 1253 { 1254 var result = new BX.Promise(); 1255 1256 if(this.initialized) 1257 { 1258 result.resolve(); 1259 return result; 1260 } 1261 1262 this.initializationPromises.push(result); 1263 return result; 1264 }; 1265 1266 BX.PhoneNumber.Input.prototype.getValue = function() 1267 { 1268 return _stripNonSignificantChars(this.inputNode.value); 1269 }; 1270 1271 BX.PhoneNumber.Input.prototype.getFormattedValue = function() 1272 { 1273 return this.inputNode.value; 1274 }; 1275 1276 BX.PhoneNumber.Input.prototype.getCountry = function() 1277 { 1278 return this.formatter.country || this.formatter.defaultCountry; 1279 }; 1280 1281 BX.PhoneNumber.Input.prototype.getCountryCode = function() 1282 { 1283 var countryMetadata = _getCountryMetadata(this.getCountry()); 1284 return (countryMetadata ? countryMetadata['countryCode'] : false); 1285 }; 1286 1287 BX.PhoneNumber.Input.prototype.drawCountryFlag = function () 1288 { 1289 if (!this.flagNode) 1290 return; 1291 1292 var country = this.getCountry(); 1293 if (!BX.type.isNotEmptyString(country)) 1294 return; 1295 1296 country = country.toLowerCase(); 1297 BX.adjust(this.flagNode, {props: {className: this.flagNodeInitialClass + " bx-flag-" + this.flagSize + " " + country}}); 1298 }; 1299 1300 BX.PhoneNumber.Input.prototype.tryRedrawCountryFlag = function() 1301 { 1302 if (this._countryBefore !== this.getCountry()) 1303 { 1304 this.drawCountryFlag(); 1305 1306 this.callbacks.countryChange({ 1307 country: this.getCountry(), 1308 countryCode: this.getCountryCode() 1309 }); 1310 } 1311 }; 1312 1313 BX.PhoneNumber.Input.prototype._onKeyDown = function (e) 1314 { 1315 if(!e.key) 1316 return; 1317 var selectedCount = this.inputNode.selectionEnd - this.inputNode.selectionStart; 1318 //skip letters 1319 if(e.key === plusChar) 1320 { 1321 //allow + char only in the beginning 1322 if(this.inputNode.selectionStart !== 0) 1323 { 1324 e.preventDefault(); 1325 e.stopPropagation(); 1326 return; 1327 } 1328 } 1329 else if(e.key.length === 1 && e.key.search(/[\d,;#]/) !== 0 && !e.ctrlKey && !e.metaKey) 1330 { 1331 e.preventDefault(); 1332 e.stopPropagation(); 1333 return; 1334 } 1335 1336 var digitsPositions = _getDigitPositions(this.inputNode.value); 1337 1338 //remember cursor position before keyboard input 1339 this._lastCaretPosition = this.inputNode.selectionStart; 1340 this._digitsToTheLeft = _countMatches(significantChars, this.inputNode.value.substr(0, this._lastCaretPosition)); 1341 this._digitsToTheRight = _countMatches(significantChars, this.inputNode.value.substr(this._lastCaretPosition)); 1342 this._digitsCount = _countMatches(significantChars, this.inputNode.value); 1343 this._countryBefore = this.getCountry(); 1344 1345 if(selectedCount > 0) 1346 { 1347 var selectedFragment = this.inputNode.value.substr(this.inputNode.selectionStart, selectedCount); 1348 this._selectedDigitsBeforeAction = _countMatches(significantChars, selectedFragment); 1349 } 1350 else 1351 { 1352 this._selectedDigitsBeforeAction = 0; 1353 } 1354 1355 //move cursor so it would delete digit instead of formatting 1356 var newCaretPosition = null; 1357 if(e.key === 'Backspace' && selectedCount === 0) 1358 { 1359 newCaretPosition = digitsPositions[this._digitsToTheLeft - 1] + 1; 1360 } 1361 1362 if(e.key === 'Delete' && selectedCount === 0 && this._digitsToTheRight > 0) 1363 { 1364 newCaretPosition = digitsPositions[this._digitsToTheLeft]; 1365 } 1366 1367 if(newCaretPosition !== null) 1368 { 1369 this.inputNode.setSelectionRange(newCaretPosition, newCaretPosition); 1370 } 1371 }; 1372 1373 BX.PhoneNumber.Input.prototype._onInput = function(e) 1374 { 1375 var caretPosition = null; 1376 1377 if(this.formatter) 1378 { 1379 var formattedValue = this.formatter.format(this.inputNode.value); 1380 var digitsPositions = _getDigitPositions(formattedValue); 1381 var digitsBefore = this._digitsCount; 1382 var digitsDeleted = this._selectedDigitsBeforeAction; 1383 var digitsAfter = _countMatches(significantChars, formattedValue); 1384 var digitsDelta = digitsAfter - digitsBefore; 1385 var digitsInserted = digitsDelta + digitsDeleted; 1386 1387 // restore caret position 1388 if(this._lastCaretPosition !== null) 1389 { 1390 switch (e.inputType) 1391 { 1392 case 'deleteContentBackward': 1393 //backspace is pressed, need to move cursor one position left if one symbol was deleted 1394 if(digitsDelta == -1) 1395 caretPosition = digitsPositions[this._digitsToTheLeft + digitsDelta - 1] + 1; 1396 else 1397 caretPosition = digitsPositions[this._digitsToTheLeft]; 1398 break; 1399 case 'deleteContentForward': 1400 //delete is pressed, don't move cursor 1401 if(this._digitsToTheLeft === 0) 1402 { 1403 caretPosition = digitsPositions[0]; 1404 } 1405 else 1406 { 1407 caretPosition = digitsPositions[this._digitsToTheLeft - 1] + 1; 1408 } 1409 break; 1410 case 'insertText': 1411 case 'insertFromPaste': 1412 //move caret after inserted digits 1413 caretPosition = digitsPositions[this._digitsToTheLeft - 1 + digitsInserted] + 1; 1414 1415 break; 1416 } 1417 } 1418 1419 this.inputNode.value = formattedValue; 1420 if(caretPosition !== null) 1421 { 1422 this.inputNode.setSelectionRange(caretPosition, caretPosition); 1423 } 1424 1425 this.callbacks.change({ 1426 value: this.getValue(), 1427 formattedValue: this.getFormattedValue(), 1428 country: this.getCountry(), 1429 countryCode: this.getCountryCode() 1430 }); 1431 1432 this.tryRedrawCountryFlag(); 1433 } 1434 this._lastCaretPosition = null; 1435 }; 1436 1437 BX.PhoneNumber.Input.prototype._onFlagClick = function (e) 1438 { 1439 /*if(this.formatter.nationalNumber != '') 1440 return;*/ 1441 1442 this.selectCountry({ 1443 node: this.flagNode, 1444 countryPopupHeight: this.countryPopupHeight, 1445 countryPopupClassName: this.countryPopupClassName, 1446 countryTopList: this.countryTopList, 1447 onSelect: this._onCountrySelect.bind(this) 1448 }); 1449 }; 1450 1451 BX.PhoneNumber.Input.prototype._onCountrySelect = function(e) 1452 { 1453 var country = e.country; 1454 if(country === this.getCountry()) 1455 return false;
1456 1457 this.formatter.replaceCountry(country); 1458 this.inputNode.value = this.formatter.getFormattedNumber(); 1459 this.drawCountryFlag(); 1460 this.callbacks.change({ 1461 value: this.getValue(), 1462 formattedValue: this.getFormattedValue(), 1463 country: this.getCountry(), 1464 countryCode: this.getCountryCode() 1465 }); 1466 this.callbacks.countryChange({ 1467 country: this.getCountry(), 1468 countryCode: this.getCountryCode() 1469 }); 1470 BX.userOptions.save('main', 'phone_number', 'default_country', country); 1471 }; 1472 1473 BX.PhoneNumber.Input.prototype.loadCountries = function() 1474 { 1475 var result = new BX.Promise(); 1476 if(this.countries) 1477 { 1478 result.fulfill(); 1479 return result; 1480 } 1481 1482 BX.ajax.runAction("main.phonenumber.getCountries").then(function(response) 1483 { 1484 this.countries = response.data; 1485 result.fulfill(); 1486 }.bind(this)).catch(function(response) 1487 { 1488 if(response.errors) 1489 { 1490 response.errors.map(function(error) 1491 { 1492 console.error(error.message); 1493 }); 1494 } 1495 else 1496 { 1497 console.error(response); 1498 } 1499 }); 1500 return result; 1501 }; 1502 1503 BX.PhoneNumber.Input.prototype.selectCountry = function (params) 1504 { 1505 var self = this; 1506 var onSelect = BX.type.isFunction(params.onSelect) ? params.onSelect : BX.DoNothing; 1507 var popupContent = BX.create("span", { 1508 events: { 1509 click: BX.delegateEvent( 1510 { 1511 attribute: 'data-country', 1512 }, 1513 function() 1514 { 1515 self.countrySelectPopup.close(); 1516 onSelect({ 1517 country: this.getAttribute('data-country') 1518 }); 1519 } 1520 ) 1521 } 1522 }); 1523 1524 var separator = null; 1525 var topList = {}; 1526 if(params.countryTopList && params.countryTopList.length > 0) 1527 { 1528 separator = popupContent.appendChild( 1529 BX.create('span', {props: {className: 'main-phonenumber-country-separator'}}) 1530 ); 1531 } 1532 1533 this.loadCountries().then(function() 1534 { 1535 self.countries.forEach(function(countryDescriptor) 1536 { 1537 var country = countryDescriptor.CODE; 1538 var countryCode = _getCountryCode(country); 1539 1540 if(!countryCode) 1541 return; 1542 1543 1544 var countryNode = popupContent.appendChild(BX.create("div", { 1545 props: {className: "main-phonenumber-country"}, 1546 attrs: {'data-country': countryDescriptor.CODE}, 1547 children: [ 1548 BX.create("span", { 1549 props: {className: "main-phonenumber-country-flag bx-flag-16 " + country.toLowerCase()} 1550 }), 1551 BX.create("span", { 1552 props: {className: "main-phonenumber-country-name"}, 1553 text: countryDescriptor.NAME + ' (+' + countryCode + ')' 1554 }) 1555 ] 1556 })); 1557 1558 if(params.countryTopList.indexOf(countryDescriptor.CODE) >= 0) 1559 { 1560 topList[countryDescriptor.CODE] = countryNode.cloneNode(true); 1561 } 1562 }); 1563 1564 if(params.countryTopList && params.countryTopList.length > 0) 1565 { 1566 params.countryTopList.forEach(function(countryCode) 1567 { 1568 if(typeof topList[countryCode] !== 'undefined') 1569 { 1570 popupContent.insertBefore(topList[countryCode], separator); 1571 } 1572 }); 1573 1574 if (popupContent.firstChild === separator) 1575 { 1576 popupContent.removeChild(separator); 1577 } 1578 } 1579 1580 self.countrySelectPopup = new BX.PopupWindow( 1581 'phoneNumberInputSelectCountry', 1582 params.node, 1583 { 1584 className: params.countryPopupClassName || '', 1585 autoHide: true, 1586 closeByEsc: true, 1587 bindOptions: { 1588 position: 'top' 1589 }, 1590 height: params.countryPopupHeight, 1591 offsetRight: 35, 1592 padding: 0, 1593 contentPadding: 10, 1594 angle: { 1595 offset: 33 1596 }, 1597 overlay: { 1598 backgroundColor: 'white', 1599 opacity: 0 1600 }, 1601 content: popupContent, 1602 events: { 1603 onPopupClose : function() 1604 { 1605 self.countrySelectPopup.destroy(); 1606 }, 1607 onPopupDestroy: function () 1608 { 1609 self.countrySelectPopup = null; 1610 } 1611 } 1612 } 1613 ); 1614 self.countrySelectPopup.show(); 1615 }); 1616 }; 1617 1618// Internal functions 1619 1620 var ShortNumberFormatter = { 1621 templates: { 1622 3: 'x-xx', 1623 4: 'xx-xx', 1624 5: 'x-xx-xx', 1625 6: 'xx-xx-xx', 1626 7: 'xxx-xx-xx' 1627 }, 1628 1629 /** 1630 * 1631 * @param {string} rawNumber 1632 * @return string 1633 */ 1634 format: function(rawNumber) 1635 { 1636 var template = this.templates[rawNumber.length]; 1637 if(!template) 1638 { 1639 return rawNumber; 1640 } 1641 1642 var i = 0; 1643 var pattern = new RegExp(template.replace(/[^x]/g, "").replace(/x/g, "(\\d)")); 1644 var format = template.replace(/x/g, function (){ return "$" + ++i;}); 1645 1646 return rawNumber.replace(pattern, format); 1647 }, 1648 1649 /** 1650 * Return true if the phone number could be formatted using this formatter and false otherwise. 1651 * @param {string} rawNumber 1652 * @return boolean 1653 */ 1654 isApplicable: function(rawNumber) 1655 { 1656 return /^\d{3,7}$/.test(rawNumber); 1657 } 1658 }; 1659 1660 /** 1661 * Extracts phone number from the input string. 1662 * @param {string} phoneNumber Phone number. 1663 * @return string 1664 */ 1665 var _extractFormattedPhoneNumber = function(phoneNumber) 1666 { 1667 if (!phoneNumber || phoneNumber.length > MAX_INPUT_STRING_LENGTH) 1668 { 1669 return ''; 1670 } 1671 1672 var startsAt = phoneNumber.search(new RegExp(phoneNumberStartPattern)); 1673 1674 // Attempt to extract a possible number from the string passed in 1675 if (startsAt < 0) 1676 { 1677 return ''; 1678 } 1679 1680 var result = phoneNumber.substr(startsAt); 1681 result = result.replace(new RegExp(afterPhoneNumberEndPattern), ''); 1682 return result; 1683 }; 1684 1685 /** 1686 * Returns country code and local number for the provided international phone number. 1687 * @param {string} phoneNumber Phone number in international format. 1688 * @return object|boolean 1689 */ 1690 var _parsePhoneNumberAndCountryPhoneCode = function(phoneNumber) 1691 { 1692 phoneNumber = _stripNonSignificantChars(phoneNumber); 1693 if(!phoneNumber) 1694 return false;
1695 1696 // If this is not an international phone number, 1697 // then don't extract country phone code. 1698 if (phoneNumber[0] !== plusChar) 1699 { 1700 return { 1701 'countryCode': '', 1702 'localNumber': phoneNumber 1703 }; 1704 } 1705 1706 // Strip the leading '+' sign 1707 phoneNumber = phoneNumber.substr(1); 1708 1709 // Fast abortion: country codes do not begin with a '0' 1710 if (phoneNumber[0] === '0') 1711 { 1712 return false; 1713 } 1714 1715 for (var i = MAX_LENGTH_COUNTRY_CODE; i > 0; i--) 1716 { 1717 var countryCode = phoneNumber.substr(0, i); 1718 if(_isValidCountryCode(countryCode)) 1719 { 1720 return { 1721 'countryCode': countryCode, 1722 'localNumber': phoneNumber.substr(i) 1723 }; 1724 } 1725 } 1726 return false; 1727 }; 1728 1729 /** 1730 * Returns true if the specified string matches general phone number pattern. 1731 * @param {string} phoneNumber Phone number. 1732 * @return boolean 1733 */ 1734 var _isViablePhoneNumber = function(phoneNumber) 1735 { 1736 return phoneNumber.length >= MIN_LENGTH_FOR_NSN && (phoneNumber.search(new RegExp(validPhoneNumberPattern)) !== -1); 1737 }; 1738 1739 /** 1740 * 1741 * @param {string} phoneNumber 1742 * @private 1743 */ 1744 var _stripExtension = function(phoneNumber) 1745 { 1746 var extension = ""; 1747 var extensionSeparator = ""; 1748 var separatorPosition = phoneNumber.search(new RegExp("[" + extensionSeparators + "]")); 1749 1750 if(separatorPosition >= 0) 1751 { 1752 extensionSeparator = phoneNumber[separatorPosition]; 1753 extension = phoneNumber.substr(separatorPosition); 1754 phoneNumber = phoneNumber.substr(0, separatorPosition); 1755 } 1756 1757 return { 1758 extensionSeparator: extensionSeparator, 1759 extension: _stripEverythingElse(extension, extensionSymbols + validDigits), 1760 phoneNumber: phoneNumber 1761 }; 1762 }; 1763 1764 /** 1765 * Returns metadata for the first country with specified countryCode. 1766 * @param {string} countryCode Phone code of the country 1767 * @return object | false 1768 */ 1769 var _getMetadataByCountryCode = function(countryCode) 1770 { 1771 if(!_isValidCountryCode(countryCode)) 1772 { 1773 return false; 1774 } 1775 1776 var countries = _getCountriesByCode(countryCode); 1777 return _getCountryMetadata(countries[0]); 1778 }; 1779 1780 /** 1781 * Returns 2-symbol country code by localNumber. 1782 * @param {string} countryCode Phone code of the country. 1783 * @param {string} localNumber Local phone number. 1784 * @return string|boolean 1785 */ 1786 var _findCountry = function(countryCode, localNumber) 1787 { 1788 if(!countryCode || !localNumber) 1789 return false; 1790 1791 var possibleCountries = _getCountriesByCode(countryCode); 1792 var possibleCountry; 1793 var countryMetadata; 1794 if(possibleCountries.length === 1) 1795 { 1796 return possibleCountries[0]; 1797 } 1798 1799 for (var i = 0; i < possibleCountries.length; i++) 1800 { 1801 possibleCountry = possibleCountries[i]; 1802 countryMetadata = _getCountryMetadata(possibleCountry); 1803 1804 // Check leading digits first 1805 if(countryMetadata.hasOwnProperty('leadingDigits')) 1806 { 1807 var leadingDigitsRegex = '^(' + countryMetadata['leadingDigits'] + ')'; 1808 if(localNumber.match(new RegExp(leadingDigitsRegex))) 1809 { 1810 return possibleCountry; 1811 } 1812 } 1813 // Else perform full validation with all of those bulky fixed-line/mobile/etc regular expressions. 1814 else if(_getNumberType(localNumber, possibleCountry)) 1815 { 1816 return possibleCountry; 1817 } 1818 } 1819 1820 return false; 1821 }; 1822 1823 /** 1824 * Returns type of the specified number. 1825 * @param {string} localNumber Local phone number. 1826 * @param {string} country 2-symbol country code. 1827 * @return string|boolean 1828 */
1829 var _getNumberType = function(localNumber, country) 1830 { 1831 // Check that the number is valid for this country 1832 var countryMetadata = _getCountryMetadata(country); 1833 var possibleType; 1834 if(!countryMetadata) 1835 return false; 1836 1837 if(!BX.type.isNotEmptyString(localNumber)) 1838 return false; 1839 1840 if((countryMetadata['generalDesc'] && countryMetadata['generalDesc']['nationalNumberPattern'])) 1841 { 1842 if(!localNumber.match(new RegExp('^(?:' + countryMetadata['generalDesc']['nationalNumberPattern'] + ')$'))) 1843 return false; 1844 } 1845 1846 var possibleTypes = ['noInternationalDialling', 'areaCodeOptional', 'fixedLine', 'mobile', 'pager', 'tollFree', 'premiumRate', 'sharedCost', 'personalNumber', 'voip', 'uan', 'voicemail']; 1847 for(var i = 0; i < possibleTypes.length; i++) 1848 { 1849 possibleType = possibleTypes[i]; 1850 if((countryMetadata[possibleType] && countryMetadata[possibleType]['nationalNumberPattern'])) 1851 { 1852 // skip checking possible lengths for now 1853 1854 if(localNumber.match(new RegExp('^' + countryMetadata[possibleType]['nationalNumberPattern'] + '$'))) 1855 { 1856 return possibleType; 1857 } 1858 } 1859 } 1860 return false; 1861 }; 1862 1863 /** 1864 * Strips national prefix from the specified phone number. Returns true if national prefix 1865 * was stripped and false otherwise. 1866 * @param {string} phoneNumber Local phone number. 1867 * @param {object} countryMetadata Country metadata. 1868 * @return string 1869 */ 1870 var _stripNationalPrefix = function(phoneNumber, countryMetadata) 1871 { 1872 var nationalPrefixForParsing = countryMetadata.hasOwnProperty('nationalPrefixForParsing') ? countryMetadata['nationalPrefixForParsing']: countryMetadata['nationalPrefix']; 1873 1874 if(phoneNumber == '' || nationalPrefixForParsing == '') 1875 return phoneNumber; 1876 1877 var nationalPrefixRegex = '^(?:' + nationalPrefixForParsing + ')'; 1878 var nationalPrefixMatches = phoneNumber.match(new RegExp(nationalPrefixRegex)); 1879 if(!nationalPrefixMatches) 1880 { 1881 //if national prefix is omitted, nothing to strip 1882 return phoneNumber; 1883 } 1884 1885 var nationalPrefixTransformRule = countryMetadata['nationalPrefixTransformRule']; 1886 var nationalSignificantNumber; 1887 if(nationalPrefixTransformRule && nationalPrefixMatches.length > 1) 1888 { 1889 nationalSignificantNumber = phoneNumber.replace(nationalPrefixRegex, nationalPrefixTransformRule); 1890 } 1891 else 1892 { 1893 // No transformation is required, just strip the prefix 1894 nationalSignificantNumber = phoneNumber.substr(nationalPrefixMatches[0].length); 1895 } 1896 1897 return nationalSignificantNumber; 1898 }; 1899 1900 var _isNumberValid = function(phoneNumber, countryMetadata) 1901 { 1902 var nationalNumberRegex = new RegExp('^(?:' + countryMetadata['generalDesc']['nationalNumberPattern'] + ')$'); 1903 if(phoneNumber.match(nationalNumberRegex, phoneNumber)) 1904 return true; 1905 else 1906 return false; 1907 }; 1908 1909 /** 1910 * Phone number is possible if there is at least one format, suitable for formatting this number. 1911 * @param phoneNumber 1912 * @param countryMetadata 1913 * @param isInternational 1914 * @param hasNationalPrefix 1915 * @private 1916 */ 1917 var _isNumberPossible = function(phoneNumber, countryMetadata, isInternational, hasNationalPrefix) 1918 { 1919 if(!countryMetadata['availableFormats']) 1920 return true; 1921 1922 for(var i = 0; i < countryMetadata.availableFormats.length; i++) 1923 { 1924 var format = countryMetadata.availableFormats[i]; 1925 if(isInternational && format['intlFormat'] === 'NA') 1926 continue; 1927 1928 if(hasNationalPrefix) 1929 { 1930 var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, countryMetadata); 1931 if(nationalPrefixFormattingRule && nationalPrefixFormattingRule.search(/\$NP/) === -1) 1932 continue; 1933 } 1934 1935 if(format['leadingDigits'] && !_matchLeadingDigits(phoneNumber, format['leadingDigits'])) 1936 continue 1937 1938 return true; 1939 } 1940 1941 return false; 1942 }; 1943 1944 /** 1945 * Strips country code from the number. Returns true if country code was stripped or false otherwise. 1946 * @param {string} phoneNumber Phone number. 1947 * @param {object} countryMetadata Country metadata. 1948 * @return string 1949 */ 1950 var _stripCountryCode = function(phoneNumber, countryMetadata) 1951 { 1952 var countryCode = countryMetadata['countryCode']; 1953 if(phoneNumber.search(countryCode) !== 0) 1954 return phoneNumber; 1955 1956 var possibleLocalNumber = phoneNumber.substr(countryCode.length); 1957 var nationalNumberRegex = new RegExp('^(?:' + countryMetadata['generalDesc']['nationalNumberPattern'] + ')$'); 1958 1959 if(phoneNumber.match(nationalNumberRegex) && !possibleLocalNumber.match(nationalNumberRegex)) 1960 { 1961 /* 1962 If the original number (before stripping national prefix) was viable, and the resultant number is not, 1963 then prefer the original phone number. This is because for some countries (e.g. Russia) the same digit 1964 could be both a national prefix and a leading digit of a valid national phone number, like `8` is the
1965 national prefix for Russia and both `8 800 555 35 35` and `800 555 35 35` are valid numbers. 1966 */ 1967 return phoneNumber; 1968 } 1969 1970 return possibleLocalNumber; 1971 }; 1972 1973 var _isValidCountryCode = function(countryCode) 1974 { 1975 countryCode = countryCode.toString(); 1976 return codeToCountries.hasOwnProperty(countryCode); 1977 }; 1978 1979 var _getCountriesByCode = function(countryCode) 1980 { 1981 countryCode = countryCode.toString(); 1982 return codeToCountries.hasOwnProperty(countryCode) ? codeToCountries[countryCode] : []; 1983 }; 1984 1985 var _getMainCountryForCode = function(countryCode) 1986 { 1987 countryCode = countryCode.toString(); 1988 return codeToCountries.hasOwnProperty(countryCode) ? codeToCountries[countryCode][0] : false; 1989 }; 1990 1991 var _getCountryMetadata = function(country) 1992 { 1993 country = country.toUpperCase(); 1994 return metadata.hasOwnProperty(country) ? metadata[country] : false; 1995 }; 1996 1997 var _getCountryCode = function(country) 1998 { 1999 country = country.toUpperCase(); 2000 return metadata.hasOwnProperty(country) ? metadata[country]['countryCode'] : false; 2001 }; 2002 2003 var _getInternationalFormat = function(format) 2004 { 2005 if(format.hasOwnProperty('intlFormat')) 2006 { 2007 if(format['intlFormat'] === 'NA') 2008 return false; 2009 else 2010 return format['intlFormat']; 2011 } 2012 return format['format']; 2013 }; 2014 2015 var _getAvailableFormats = function(countryMetadata) 2016 { 2017 if(BX.type.isArray(countryMetadata['availableFormats'])) 2018 return countryMetadata['availableFormats']; 2019 2020 var countryCode = countryMetadata['countryCode']; 2021 var countriesForCode = _getCountriesByCode(countryCode); 2022 var mainCountry = countriesForCode[0]; 2023 var mainCountryMetadata = _getCountryMetadata(mainCountry); 2024 return BX.type.isArray(mainCountryMetadata['availableFormats']) ? mainCountryMetadata['availableFormats'] : []; 2025 2026 }; 2027 2028 var _getNationalPrefix = function(countryMetadata, stripNonDigits) 2029 { 2030 if(!countryMetadata.hasOwnProperty('nationalPrefix')) 2031 { 2032 return ''; 2033 } 2034 2035 var nationalPrefix = countryMetadata['nationalPrefix']; 2036 if (stripNonDigits) 2037 { 2038 nationalPrefix = _stripLetters(nationalPrefix); 2039 } 2040 return nationalPrefix; 2041 }; 2042 2043 var _getNationalPrefixFormattingRule = function (format, countryMetadata) 2044 { 2045 if(format.hasOwnProperty('nationalPrefixFormattingRule')) 2046 { 2047 return format['nationalPrefixFormattingRule']; 2048 } 2049 else 2050 { 2051 var countryCode = countryMetadata['countryCode']; 2052 var countriesForCode = _getCountriesByCode(countryCode); 2053 var mainCountry = countriesForCode[0]; 2054 var mainCountryMetadata = _getCountryMetadata(mainCountry); 2055 2056 return mainCountryMetadata['nationalPrefixFormattingRule'] || ''; 2057 } 2058 }; 2059 2060 var _numberContainsNationalPrefix = function(phoneNumber, nationalPrefix, countryMetadata) 2061 { 2062 if (phoneNumber.indexOf(nationalPrefix) === 0) 2063 { 2064 // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the national prefix 2065 // when written without it (e.g. 0777123) if we just do prefix matching. To tackle that, we 2066 // check the validity of the number if the assumed national prefix is removed (777123 won't 2067 // be valid in Japan). 2068 var numberWithoutPrefix = phoneNumber.substr(nationalPrefix.length); 2069 return BX.PhoneNumberParser.getInstance()._realParse(numberWithoutPrefix, countryMetadata['id']).isValid(); 2070 } 2071 else 2072 { 2073 return false; 2074 } 2075 }; 2076 2077 var _isNationalPrefixOptional = function(format, countryMetadata) 2078 { 2079 if(format.hasOwnProperty('nationalPrefixOptionalWhenFormatting')) 2080 return format['nationalPrefixOptionalWhenFormatting']; 2081 else if(countryMetadata.hasOwnProperty('nationalPrefixOptionalWhenFormatting')) 2082 return countryMetadata['nationalPrefixOptionalWhenFormatting']; 2083 else 2084 return false; 2085 }; 2086 2087 /** 2088 * National prefix is supported by the format if: 2089 * 1. Format and country metadata do not have nationalPrefixFormattingRule. 2090 * 2. OR Format or country metadata contains nationalPrefixFormattingRule and this formatting rule contains "$NP" 2091 * @param format 2092 * @param countryMetadata 2093 * @returns {boolean} 2094 * @private 2095 */ 2096 var _isNationalPrefixSupported = function(format, countryMetadata) 2097 { 2098 var nationalPrefixFormattingRule = _getNationalPrefixFormattingRule(format, countryMetadata); 2099 2100 return (!nationalPrefixFormattingRule || nationalPrefixFormattingRule.search(/\$NP/) !== -1); 2101 }; 2102 2103 var _matchLeadingDigits = function(phoneNumber, leadingDigits) 2104 { 2105 var re; 2106 var matches; 2107 if(BX.type.isArray(leadingDigits)) 2108 { 2109 for (var i = 0; i < leadingDigits.length; i++) 2110 { 2111 re = new RegExp('^(' + leadingDigits[i] + ')'); 2112 matches = phoneNumber.match(re); 2113 if(matches) 2114 { 2115 return matches; 2116 } 2117 } 2118 } 2119 else 2120 { 2121 re = new RegExp('^(' + leadingDigits + ')'); 2122 matches = phoneNumber.match(re); 2123 if(matches) 2124 { 2125 return matches; 2126 } 2127 } 2128 return false;
2129 }; 2130 2131 var _getFormatFormat = function(format, international) 2132 { 2133 if(international && format.hasOwnProperty('intlFormat')) 2134 return format['intlFormat']; 2135 else 2136 return format['format']; 2137 }; 2138 2139 /** 2140 * Strips all letters from the string. 2141 * @param {string} str Input string. 2142 * @return string 2143 */ 2144 var _stripLetters = function(str) 2145 { 2146 return _stripEverythingElse(str, validDigits) 2147 }; 2148 2149 var _stripNonSignificantChars = function(str) 2150 { 2151 return _stripEverythingElse(str, significantChars); 2152 }; 2153 2154 var _stripEverythingElse = function(str, allowedSymbols) 2155 { 2156 return str.replace(new RegExp("[^" + allowedSymbols + "]", "g"), ""); 2157 }; 2158 2159 var _countMatches = function(needle, haystack) 2160 { 2161 var matches = haystack.match(needle instanceof RegExp ? needle : new RegExp("[" + needle + "]", 'g')); 2162 return matches ? matches.length : 0; 2163 }; 2164 2165 var _getDigitPositions = function(str) 2166 { 2167 var re = new RegExp("[" + significantChars + "]", "g"); 2168 var result = []; 2169 var match; 2170 2171 while((match = re.exec(str)) !== null) 2172 { 2173 2174 result.push(match.index) 2175 } 2176 return result; 2177 }; 2178 2179 function _repeat(str, times) 2180 { 2181 var result = ''; 2182 2183 if(times <= 0) 2184 return ''; 2185 2186 for(var i = 0; i < times; i++) result += str; 2187 return result; 2188 } 2189})();
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.