1/** 2 * @license 3 * jQuery Tools Validator 1.2.4 - HTML5 is here. Now use it. 4 * 5 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. 6 * 7 * http://flowplayer.org/tools/form/validator/ 8 * 9 * Since: Mar 2010 10 * Date: Sun Aug 15 08:16:31 2010 +0000 11 **/ 12/*jslint evil: true */ 13/* 14* DDCShanonP Note: This plugin has been edited multiple times by DDC for various fixes as the plug-in is no longer 15* under active development. Probably a good candidate for replacement in the future. 16* 17* dttimothyn : I modified the invalidate() method to where you can add custom errors in the message stack. 18* Prior to this change, you had to clone and extend the validator object 19* 20* Miguel.Rosas: Added a new rule that will validate comment section (textarea) by limiting to a max of 5,000 chars. 21* Also did the same thing for name and lastname filed with a 50 char limit. 22* usage: 23* validator.invalidate({"myFieldName" : "my custom error message"}); 24* validator.checkValidity(); // attaches the error to myFieldName on your form 25* 26*/ 27(function($) { 28 29 $.tools = $.tools || {version: '1.2.4'}; 30 31 // globals 32 var typeRe = /\[type=([a-z]+)\]/, 33 regexList, 34 v; 35 36 v = $.tools.validator = { 37 38 conf: { 39 grouped: false, // show all error messages at once inside the container 40 effect: 'default', // show/hide effect for error message. only 'default' is built-in 41 errorClass: 'invalid', // input field class name in case of validation error 42 43 // Remove or set to false to disable 44 // US809696: Added validation for char limit. 45 charLimit: { 46 "textarea": 5000, 47 "input[name*='contact.firstName']": 50, 48 "input[name*='contact.lastName']": 50, 49 "input[name*='contact.address.postalCode']": 10, 50 "input[name*='contact.email']": 100, 51 "input[name*='contact.phone']": 100 52 }, 53 54 // when to check for validity? 55 inputEvent: null, // change, blur, keyup, null 56 errorInputEvent: 'keyup', // change, blur, keyup, null 57 formEvent: 'submit', // submit, null 58 lang: 'en', // default language for error messages 59 message: '<div/>', 60 messageAttr: 'data-message', // name of the attribute for overridden error message 61 messageClass: 'error', // error message element's class name 62 offset: [0, 0], 63 position: 'center right', 64 singleError: false, // validate all inputs at once 65 speed: 'normal', // message's fade-in speed 66 // Dictionary of regex 67 // Validator should be set to ascii by default (standard RegExp) 68 // If form sets validator to utf8 via charset attr on form (XRegExp) 69 isAscii: true, 70 regexList: { 71 email: { 72 ascii: /^([a-z0-9_\.\-\+]+)@([\da-zA-Z\.\-]+)\.([a-zA-Z\.]{2,6})$/i, 73 utf8: '^([\\pL\\pN_\\.\\-\\+]+)@([\\pN\\pL\\.\\-]+)\\.([a-zA-Z\\.]{2,6})$' 74 }, 75 url: { 76 ascii: /^(https?:\/\/)?[\da-z\.\-]+\.[a-z\.]{2,6}[#\?\/\w \.\-=]*$/i, 77 utf8: '^(https?:\/\/)?[\\d\\pL\\.\\-]+\.[a-z\\.]{2,6}[#\?\/\w \\.\\-=]*$' 78 }, 79 containsUrl: { 80 ascii: /(https?:\/\/)?[\da-z\.\-]+\.[a-z\.]{2,6}/i, 81 utf8: '(https?:\\/\\/)?[\\da-z\\.\\-]+\\.[a-z\\.]{2,6}' 82 }, 83 number: { 84 ascii: /^-?[0-9]*(\.[0-9]+)?$/, 85 utf8: '^-?[0-9]*(\\.[0-9]+)?$' 86 } 87 } 88 }, 89 90 /* The Error Messages */ 91 messages: { 92 "*": { en: DDC.i18n.getLabel('PLEASE_CORRECT_THIS_VALUE') } 93 }, 94 95 localize: function(lang, messages) { 96 $.each(messages, function(key, msg) { 97 v.messages[key] = v.messages[key] || {}; 98 v.messages[key][lang] = msg; 99 }); 100 }, 101 102 localizeFn: function(key, messages) { 103 v.messages[key] = v.messages[key] || {}; 104 $.extend(v.messages[key], messages); 105 }, 106 107 /** 108 * Adds a new validator 109 */ 110 fn: function(matcher, msg, fn) { 111 112 // no message supplied 113 if ($.isFunction(msg)) { 114 fn = msg; 115 116 // message(s) on second argument 117 } else { 118 if (typeof msg == 'string') { msg = {en: msg}; } 119 this.messages[matcher.key || matcher] = msg; 120 } 121 122 // add validator to the arsenal 123 fns.push([matcher, fn]); 124 }, 125 126 /* Add new show/hide effect */ 127 addEffect: function(name, showFn, closeFn) { 128 effects[name] = [showFn, closeFn]; 129 } 130 131 }; 132 133 /* calculate error message position relative to the input */ 134 function getPosition(trigger, el, conf) { 135 136 // get origin top/left position 137 var top = trigger.offset().top, 138 left = trigger.offset().left, 139 pos = conf.position.split(/,?\s+/), 140 y = pos[0], 141 x = pos[1]; 142 143 top -= el.outerHeight() - conf.offset[0]; 144 left += trigger.outerWidth() + conf.offset[1]; 145 146 // adjust Y 147 var height = el.outerHeight() + trigger.outerHeight(); 148 if (y == 'center') { top += height / 2; } 149 if (y == 'bottom') { top += height; } 150 151 // adjust X 152 var width = trigger.outerWidth(); 153 if (x == 'center') { left -= (width + el.outerWidth()) / 2; } 154 if (x == 'left') { left -= width; } 155 156 return {top: top, left: left}; 157 } 158 159 var fns = [], effects = { 160 161 'default' : [ 162 163 // show errors function 164 function(errs) { 165 166 var conf = this.getConf(); 167 168 // loop errors 169 $.each(errs, function(i, err) { 170 171 // add error class 172 var input = err.input; 173 input.addClass(conf.errorClass); 174 175 // get handle to the error container 176 var msg = input.data("msg.el"); 177 178 // create it if not present 179 if (!msg) { 180 msg = $(conf.message).addClass(conf.messageClass).appendTo(document.body); 181 input.data("msg.el", msg); 182 } 183 184 // clear the container 185 msg.css({visibility: 'hidden', display: 'none'}).find("span").remove(); 186 187 // populate messages 188 $.each(err.messages, function(i, m) { 189 $("<span/>").html(m).appendTo(msg); 190 }); 191 192 // make sure the width is sane (not the body's width) 193 if (msg.outerWidth() == msg.parent().width()) { 194 msg.add(msg.find("p")).css({display: 'inline'}); 195 } 196 197 // insert into correct position (relative to the field) 198 var pos = getPosition(input, msg, conf); 199 200 msg.css({ visibility: 'visible', position: 'absolute', top: pos.top, left: pos.left }) 201 .fadeIn(conf.speed); 202 }); 203 204 // hide errors function 205 }, function(inputs) { 206 207 var conf = this.getConf(); 208 inputs.removeClass(conf.errorClass).each(function() { 209 var msg = $(this).data("msg.el"); 210 if (msg) { msg.css({visibility: 'hidden'}); } 211 }); 212 } 213 ] 214 }; 215 216 /* sperial selectors */ 217 $.each("email,url,number".split(","), function(i, key) { 218 $.expr[':'][key] = function(el) { 219 return el.getAttribute("type") === key; 220 }; 221 }); 222 223 /* 224 oninvalid() jQuery plugin. 225 Usage: $("input:eq(2)").oninvalid(function() { ... }); 226 */ 227 $.fn.oninvalid = function( fn ){ 228 return this[fn ? "bind" : "trigger"]("OI", fn); 229 }; 230 231 /******* built-in HTML5 standard validators *********/ 232
233 v.fn(":email", DDC.i18n.getLabel('PLEASE_ENTER_A_VALID_EMAIL_ADDRESS'), function(el, v) { 234 return !v || this.getRegex('email', v); 235 }); 236 237 v.fn(":url", DDC.i18n.getLabel('PLEASE_ENTER_A_VALID_URL'), function(el, v) { 238 return !v || this.getRegex('url', v); 239 }); 240 241 v.fn('[data-urls-blocked="true"]', DDC.i18n.getLabel('PLEASE_DO_NOT_USE_URL'), function(el, v) { 242 return !v || !this.getRegex('containsUrl', v.replace(/\n/g, ' ')); 243 }); 244 245 v.fn(":number", DDC.i18n.getLabel('PLEASE_ENTER_A_NUMERIC_VALUE'), function(el, v) { 246 return this.getRegex('number', v); 247 }); 248 249 v.fn("[max]", DDC.i18n.getLabel('PLEASE_ENTER_A_VALUE_SMALLER_THAN_ONE_DOLLAR'), function(el, v) { 250 // skip empty values and dateinputs 251 if (v === '' || $(el).hasClass("date")) { return true; } 252 253 var max = el.attr("max"); 254 return parseFloat(v) <= parseFloat(max) ? true : [max]; 255 }); 256 257 v.fn("[min]", DDC.i18n.getLabel('PLEASE_ENTER_A_VALUE_LARGER_THAN_ONE_DOLLAR'), function(el, v) { 258 // skip empty values and dateinputs 259 if (v === '' || $(el).hasClass("date")) { return true; } 260 261 var min = el.attr("min"); 262 return parseFloat(v) >= parseFloat(min) ? true : [min]; 263 }); 264 265 // US1004597: field name included on the validation message 266 v.fn( 267 "[required]:is(input[type=checkbox]", 268 DDC.i18n.getLabel("OOPS_EXCLAMATION_YOU_MISSED_THIS_ONE"), 269 function (el, v) { 270 if (el.is(":checkbox")) { 271 return el.is(":checked"); 272 } 273 return !!v; 274 } 275 ); 276 277 v.fn("[required]:not(input[type=checkbox])", function (el) { 278 if (!el.val()) { 279 v.messages["[required]:not(input[type=checkbox])"] = { 280 en: 281 el.context.labels[0].innerText.replace("*", " ") + 282 DDC.i18n.getLabel("FORM_FIELD_IS_REQUIRED"), 283 }; 284 285 return false; 286 } 287 return true; 288 }); 289 290 v.fn("[consent]", DDC.i18n.getLabel('TCPA_CONSENT_ERROR_VERBIAGE'), function(el, v){ 291 if (el.is(":checkbox")) { return el.is(":checked"); } 292 return !!v; 293 }); 294 295 // US809696: char limit validation 296 Object.entries(v.conf.charLimit).map(function(e, i) { 297 const elem = e[0], limit = e[1]; 298 v.fn(elem, function(el) { 299 if(limit && typeof limit === 'number') 300 if(!!(el.val().length > limit)){ 301 v.messages[elem] = { en: "Oops! Character limit exceeded. " + limit + " max.<br>Current count: " + el.val().length + " characters." } 302 return false; 303 } 304 return true; 305 }); 306 }); 307 308 v.fn("[pattern]", function(el) { 309 var p = new RegExp("^" + el.attr("pattern") + "$"); 310 return p.test(el.val()); 311 }); 312 313 function Validator(inputs, form, conf) { 314 315 // private variables 316 var self = this, 317 fire = form.add(self), 318 invalidatedErrors = []; 319 320 // make sure there are input fields available 321 inputs = inputs.not(":button, :image, :reset, :submit"); 322 323 // utility function 324 function pushMessage(to, matcher, returnValue) { 325 326 // only one message allowed 327 if (!conf.grouped && to.length) { return; } 328 329 // the error message 330 var msg; 331 332 // substitutions are returned 333 if (returnValue === false || $.isArray(returnValue)) { 334 msg = v.messages[matcher.key || matcher] || v.messages["*"]; 335 msg = msg[conf.lang] || v.messages["*"].en; 336 337 // substitution 338 var matches = msg.match(/\$\d/g); 339 340 if (matches && $.isArray(returnValue)) { 341 $.each(matches, function(i) { 342 msg = msg.replace(this, returnValue[i]); 343 }); 344 } 345 346 // error message is returned directly 347 } else { 348 msg = returnValue[conf.lang] || returnValue; 349 } 350 351 to.push(msg); 352 } 353 354 // API methods 355 $.extend(self, { 356 357 getConf: function() { 358 return conf; 359 }, 360 361 getForm: function() { 362 return form; 363 }, 364 365 getInputs: function() { 366 return inputs; 367 }, 368 369 reflow: function() { 370 inputs.each(function() { 371 var input = $(this), 372 msg = input.data("msg.el"); 373 374 if (msg) { 375 var pos = getPosition(input, msg, conf); 376 msg.css({ top: pos.top, left: pos.left }); 377 } 378 }); 379 return self; 380 }, 381 382 /** @param e - for internal use only */ 383 invalidate: function(errs, e) { 384 385 // errors are given manually: { fieldName1: 'message1', fieldName2: 'message2' } 386 if (!e) { 387 var errors = []; 388 $.each(errs, function(key, val) { 389 var input = inputs.filter("[name='" + key + "']"); 390 if (input.length) { 391 392 // trigger HTML5 ininvalid event 393 input.trigger("OI", [val]); 394 395 errors.push({ input: input, messages: [val]}); 396 } 397 }); 398 399 errs = errors; 400 this.invalidatedErrors = errors; 401 e = $.Event(); 402 } 403 404 // onFail callback 405 e.type = "onFail"; 406 fire.trigger(e, [errs]); 407 408 409 // call the effect
410 if (!e.isDefaultPrevented()) { 411 effects[conf.effect][0].call(self, errs, e); 412 } 413 414 //DDCShanonp 415 //Hack to fix JIRA ticket JIRA-27191 as the plugin does not know how to handle 416 //checkbox groups and there is no event to target externally that happens after all validation 417 //is complete AND displayed in the DOM 418 //If left to function normally, all checkboxes in the required group will get a validation message 419 //even if one is selected and will pass the validation 420 var $checkboxGroupContainer = $('span.group-required'), 421 checkboxGroupErrors = $checkboxGroupContainer.find('strong.errors'), 422 isSingleCheckboxChecked = $checkboxGroupContainer.find('input[type="checkbox"]:checked').length > 0, 423 $validationMessageContainer = $('label[checkbox-group-validation-error]'); 424 425 //Clear all of the individual checkbox validation messages 426 checkboxGroupErrors.hide(); 427 428 //if none are checked, add a single validation message to the top of the group 429 if (!isSingleCheckboxChecked) { 430 //grab one already placed there by the plug in and make it visible 431 var oneError = $(checkboxGroupErrors[0]).show(); 432 //place it in the container 433 $validationMessageContainer.empty().append(oneError); 434 } else { 435 //All good, remove the single validator message 436 $validationMessageContainer.empty(); 437 } 438 //End plug in hack 439 440 return self; 441 }, 442 443 reset: function(els) { 444 els = els || inputs; 445 els.removeClass(conf.errorClass).each(function() { 446 var msg = $(this).data("msg.el"); 447 if (msg) { 448 msg.remove(); 449 $(this).data("msg.el", null); 450 } 451 }).unbind(conf.errorInputEvent || ''); 452 this.invalidatedErrors = []; 453 return self; 454 }, 455 456 destroy: function() { 457 form.unbind(conf.formEvent).unbind("reset.V"); 458 inputs.unbind(conf.inputEvent || '').unbind("change.V"); 459 return self.reset(); 460 }, 461 462 // UTF8 support 463 getRegex: function(type, value) { 464 var regex, 465 hasXreg = false, 466 conf = this.getConf(), 467 regexList = conf.regexList, 468 result = false;
469 470 if(typeof(window.XRegExp) === 'function') 471 hasXreg = true; 472 473 if(hasXreg && !conf.isAscii){ 474 regex = regexList[type]['utf8']; 475 result = XRegExp(regex, 'i').test(value); 476 } else { 477 regex = regexList[type]['ascii']; 478 result = regex.test(value); 479 } 480 481 return result; 482 }, 483 484//{{{ checkValidity() - flesh and bone of this tool 485 486 /** @returns boolean */ 487 checkValidity: function(els, e) { 488 489 els = els || inputs; 490 els = els.not(":disabled"); 491 if (!els.length) { return true; } 492 493 e = e || $.Event(); 494 495 // DE736383 / US1550942: capture the original event type before fire.trigger 496 // mutates e.type. Used below to decide whether to scroll-to-first-error. 497 var originalEventType = e.type; 498 499 // onBeforeValidate 500 // DE736383: when the passed-in event is an upstream form event (e.g. submit) 501 // that another handler already preventDefault'd (recaptcha-loader on toyota 502 // minimal layouts does this), reusing the same event for onBeforeValidate 503 // makes isDefaultPrevented return true and bail before validation runs â 504 // no scroll-to-first-error animate, no field shown to the user. Use a fresh 505 // jQuery event so onBeforeValidate's prevented state reflects only handlers 506 // of this validation cycle. 507 if (originalEventType && originalEventType !== 'onBeforeValidate') { 508 e = $.Event('onBeforeValidate'); 509 } else { 510 e.type = "onBeforeValidate"; 511 } 512 fire.trigger(e, [els]); 513 if (e.isDefaultPrevented()) { return e.result; } 514 515 var errs = this.invalidatedErrors || [], 516 event = conf.errorInputEvent + ".v"; 517 518 // loop trough the inputs 519 els.not(":radio:not(:checked)").each(function() { 520 521 // field and it's error message container 522 var msgs = [], 523 el = $(this).unbind(event).data("messages", msgs); 524 525 // loop all validator functions 526 $.each(fns, function() { 527 var fn = this, match = fn[0]; 528 529 // match found 530 if (el.filter(match).length) { 531 532 // execute a validator function 533 var returnValue = fn[1].call(self, el, el.val()); 534 535 // validation failed. multiple substitutions can be returned with an array 536 if (returnValue !== true) { 537 538 // onBeforeFail 539 e.type = "onBeforeFail"; 540 fire.trigger(e, [el, match]); 541 if (e.isDefaultPrevented()) { return false; } 542 543 // overridden custom message 544 var msg = el.attr(conf.messageAttr); 545 if (msg) { 546 msgs = [msg]; 547 return false; 548 } else if (el.filter("[name*=tcpaConsent]").length > 0){ 549 pushMessage(msgs, "[consent]", returnValue) 550 } else { 551 pushMessage(msgs, match, returnValue); 552 } 553 } 554 } 555 556 }); 557 558 if (msgs.length) { 559 560 errs.push({input: el, messages: msgs}); 561 562 // trigger HTML5 ininvalid event 563 el.trigger("OI", [msgs]); 564 565 // begin validating upon error event type (such as keyup or change) 566 if (conf.errorInputEvent) { 567 el.bind(event, function(e) { 568 self.checkValidity(el, e); 569 }); 570 } 571 } 572 573 if (conf.singleError && errs.length) { return false; } 574 575 576 }); 577 578 // validation done. now check that we have a proper effect at hand 579 var eff = effects[conf.effect]; 580 if (!eff) { throw "Validator: cannot find effect \"" + conf.effect + "\""; } 581 582 // errors found 583 if (errs.length) { 584 585 scrollInput = $(errs[0]['input']); 586 587 // Scroll to first error if not in a dialog: JIRA-10524 588 // But provide a behavior class override: SPECTRE-26 ddcorsonb 589 // 590 // DE736383 / US1550942: when checkValidity is called from a per-field 591 // event (such as input/change as the user fixes a field), running this 592 // scroll-to-first-error animate yanks the page around on every keystroke 593 // â the field the user is typing in gets pulled under the sticky header. 594 // It also interferes with submit clicks (layout shift between mousedown 595 // and mouseup makes the click miss the button). Limit the auto-scroll to
596 // form-submit-time validation, where it's actually useful. 597 // Only auto-scroll for an actual form submit. Other call sites 598 // (per-field input/change handlers, programmatic form.checkValidity() 599 // calls from tracking widgets, etc.) firing animate too cause competing 600 // scrolls â the page jitters before settling. 601 if (originalEventType === 'submit' && scrollInput.parents('.ui-dialog').size() === 0 && !scrollInput.parents('form').hasClass('validate-no-scroll')) { 602 // DE736383: account for the sticky header so the field lands 603 // below it, not underneath it. Without this the page scrolls 604 // the field to viewport top and any anchor / sticky-header 605 // watcher then re-corrects, producing a janky pop. 606 var headerAdj = (window.DDC && typeof DDC.calculateHeaderOffset === 'function') 607 ? DDC.calculateHeaderOffset() : 0; 608 if (!headerAdj) { headerAdj = 10; } 609 // US1971730: scroll to the field's group (label + input) instead of the 610 // bare input. On the adaptive mobile layout the label stacks ABOVE the 611 // input, so landing the input just below the header tucked the label under 612 // it. Targeting .form-group lands the label below the header; desktop is 613 // unaffected (the label sits beside the input there). 614 var scrollTarget = scrollInput.closest('.form-group, .control-group'); 615 if (!scrollTarget.length) { scrollTarget = scrollInput; } 616 offset = scrollTarget.offset(); 617 offset = (offset.top - headerAdj); 618 if (offset < 0) { 619 offset = 0; 620 } 621 // DE736383: rapid repeat clicks on submit queue animates back-to-back 622 // because jQuery's default animate queue is FIFO. Each click adds a new 623 // animate to the queue. By the time the user clicks again, target is 624 // usually unchanged so the queued animate has nothing to scroll to once 625 // the first one completes. User perceives this as "second click did 626 // nothing" â has to wait for the queue to drain. Calling .stop(true, false) 627 // before .animate clears any queued animations and stops the in-flight 628 // one, so each click starts fresh from current scroll position. 629 $('html, body').stop(true, false).animate({scrollTop: offset}, 500); 630 } 631 632 this.invalidatedErrors = []; 633 self.invalidate(errs, e); 634 return false; 635 636 // no errors 637 } else { 638 639 // call the effect 640 eff[1].call(self, els, e); 641 642 // onSuccess callback 643 e.type = "onSuccess"; 644 fire.trigger(e, [els]); 645 646 els.unbind(event); 647 } 648 649 return true; 650 } 651 }); 652 653 // callbacks 654 $.each("onBeforeValidate,onBeforeFail,onFail,onSuccess".split(","), function(i, name) { 655 656 // configuration 657 if ($.isFunction(conf[name])) { 658 $(self).bind(name, conf[name]); 659 } 660 661 // API methods 662 self[name] = function(fn) { 663 $(self).bind(name, fn); 664 return self; 665 }; 666 }); 667 668 // form validation 669 if (conf.formEvent) { 670 form.bind(conf.formEvent, function(e) { 671 // submit happens here 672 if (!self.checkValidity(null, e)) { 673 return e.preventDefault(); 674 } 675 // passed all validation 676 form.trigger("onPost"); 677 }); 678 } 679 680 // form reset 681 form.bind("reset.V", function() { 682 self.reset(); 683 }); 684 685 // disable browser's default validation mechanism 686 if (inputs[0] && inputs[0].validity) { 687 inputs.each(function() { 688 this.oninvalid = function() { 689 return false; 690 }; 691 }); 692 } 693 694 // Web Forms 2.0 compatibility 695 if (form[0]) { 696 form[0].checkValidity = self.checkValidity; 697 } 698 699 // input validation 700 if (conf.inputEvent) { 701 inputs.bind(conf.inputEvent, function(e) { 702 self.checkValidity($(this), e); 703 }); 704 } 705 706 // checkboxes, selects and radios are checked separately 707 inputs.filter(":checkbox, select").filter("[required]").bind("change.V", function(e) { 708 var el = $(this); 709 if (this.checked || (el.is("select") && $(this).val())) { 710 effects[conf.effect][1].call(self, el, e); 711 } 712 }); 713 714 var radios = inputs.filter(":radio").change(function(e) { 715 self.checkValidity(radios, e); 716 }); 717 718 // reposition tooltips when window is resized 719 $(window).resize(function() { 720 self.reflow(); 721 }); 722 } 723 724 // jQuery plugin initialization 725 $.fn.validator = function(conf) { 726 var instance = this.data("validator"); 727 728 // destroy existing instance 729 if (instance) { 730 instance.destroy(); 731 this.removeData("validator"); 732 } 733 734 // configuration 735 conf = $.extend(true, {}, v.conf, conf); 736 737 // selector is a form 738 if (this.is("form")) { 739 740 return this.each(function() { 741 var form = $(this); 742 // form charset for regex 743 if (form.attr('charset') && form.attr('charset') === 'utf8') 744 conf.isAscii = false;
745 746 instance = new Validator(form.find(":input"), form, conf); 747 form.data("validator", instance); 748 }); 749 } else { 750 instance = new Validator(this, this.eq(0).closest("form"), conf); 751 return this.data("validator", instance); 752 } 753 }; 754})(jQuery);
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.