1/* 2 BestInPlace (for jQuery) 3 version: 0.1.0 (01/01/2011) 4 @requires jQuery >= v1.4 5 @requires jQuery.purr to display pop-up windows 6 7 By Bernat Farrero based on the work of Jan Varwig. 8 Examples at http://bernatfarrero.com 9 10 Licensed under the MIT: 11 http://www.opensource.org/licenses/mit-license.php 12 13 Usage: 14 15 Attention. 16 The format of the JSON object given to the select inputs is the following: 17 [["key", "value"],["key", "value"]] 18 The format of the JSON object given to the checkbox inputs is the following: 19 ["falseValue", "trueValue"] 20*/ 21 22 23function BestInPlaceEditor(e) { 24 this.element = e; 25 this.initOptions(); 26 this.bindForm(); 27 this.initNil(); 28 jQuery(this.activator).bind('click', {editor: this}, this.clickHandler); 29} 30 31BestInPlaceEditor.prototype = { 32 // Public Interface Functions ////////////////////////////////////////////// 33 34 activate : function() { 35 var to_display = ""; 36 if (this.isNil()) { 37 to_display = ""; 38 } 39 else if (this.original_content) { 40 to_display = this.original_content; 41 } 42 else { 43 if (this.sanitize) { 44 to_display = this.element.text(); 45 } else { 46 to_display = this.element.html(); 47 } 48 } 49 50 this.oldValue = this.isNil() ? "" : this.element.html(); 51 this.display_value = to_display; 52 jQuery(this.activator).unbind("click", this.clickHandler); 53 this.activateForm(); 54 this.element.trigger(jQuery.Event("best_in_place:activate")); 55 }, 56 57 abort : function() { 58 this.activateText(this.oldValue); 59 jQuery(this.activator).bind('click', {editor: this}, this.clickHandler); 60 this.element.trigger(jQuery.Event("best_in_place:abort")); 61 this.element.trigger(jQuery.Event("best_in_place:deactivate")); 62 }, 63 64 abortIfConfirm : function () { 65 if (!this.useConfirm) { 66 this.abort(); 67 return; 68 } 69 70 if (confirm("Are you sure you want to discard your changes?")) { 71 this.abort(); 72 } 73 }, 74 75 update : function() { 76 var editor = this; 77 if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue) 78 { // Avoid request if no change is made 79 this.abort(); 80 return true; 81 } 82 editor.ajax({ 83 "type" : "post", 84 "dataType" : "text", 85 "data" : editor.requestData(), 86 "success" : function(data){ editor.loadSuccessCallback(data); }, 87 "error" : function(request, error){ editor.loadErrorCallback(request, error); } 88 }); 89 if (this.formType == "select") { 90 var value = this.getValue(); 91 this.previousCollectionValue = value; 92 93 jQuery.each(this.values, function(i, v) { 94 if (value == v[0]) { 95 editor.element.html(v[1]); 96 } 97 } 98 ); 99 } else if (this.formType == "checkbox") { 100 editor.element.html(this.getValue() ? this.values[1] : this.values[0]); 101 } else { 102 if (this.getValue() !== "") { 103 editor.element.text(this.getValue()); 104 } else { 105 editor.element.html(this.nil); 106 } 107 } 108 editor.element.trigger(jQuery.Event("best_in_place:update")); 109 }, 110 111 activateForm : function() { 112 alert("The form was not properly initialized. activateForm is unbound"); 113 }, 114 115 activateText : function(value){ 116 this.element.html(value); 117 if(this.isNil()) this.element.html(this.nil); 118 }, 119 120 // Helper Functions //////////////////////////////////////////////////////// 121 122 initOptions : function() { 123 // Try parent supplied info 124 var self = this; 125 self.element.parents().each(function(){ 126 $parent = jQuery(this); 127 self.url = self.url || $parent.attr("data-url"); 128 self.collection = self.collection || $parent.attr("data-collection"); 129 self.formType = self.formType || $parent.attr("data-type"); 130 self.objectName = self.objectName || $parent.attr("data-object"); 131 self.attributeName = self.attributeName || $parent.attr("data-attribute"); 132 self.activator = self.activator || $parent.attr("data-activator"); 133 self.okButton = self.okButton || $parent.attr("data-ok-button"); 134 self.okButtonClass = self.okButtonClass || $parent.attr("data-ok-button-class"); 135 self.cancelButton = self.cancelButton || $parent.attr("data-cancel-button"); 136 self.cancelButtonClass = self.cancelButtonClass || $parent.attr("data-cancel-button-class"); 137 self.nil = self.nil || $parent.attr("data-nil"); 138 self.inner_class = self.inner_class || $parent.attr("data-inner-class"); 139 self.html_attrs = self.html_attrs || $parent.attr("data-html-attrs"); 140 self.original_content = self.original_content || $parent.attr("data-original-content"); 141 self.collectionValue = self.collectionValue || $parent.attr("data-value"); 142 }); 143 144 // Try Rails-id based if parents did not explicitly supply something 145 self.element.parents().each(function(){ 146 var res = this.id.match(/^(\w+)_(\d+)$/i); 147 if (res) { 148 self.objectName = self.objectName || res[1]; 149 } 150 }); 151 152 // Load own attributes (overrides all others) 153 self.url = self.element.attr("data-url") || self.url || document.location.pathname; 154 self.collection = self.element.attr("data-collection") || self.collection; 155 self.formType = self.element.attr("data-type") || self.formtype || "input"; 156 self.objectName = self.element.attr("data-object") || self.objectName; 157 self.attributeName = self.element.attr("data-attribute") || self.attributeName; 158 self.activator = self.element.attr("data-activator") || self.element; 159 self.okButton = self.element.attr("data-ok-button") || self.okButton; 160 self.okButtonClass = self.element.attr("data-ok-button-class") || self.okButtonClass || ""; 161 self.cancelButton = self.element.attr("data-cancel-button") || self.cancelButton; 162 self.cancelButtonClass = self.element.attr("data-cancel-button-class") || self.cancelButtonClass || ""; 163 self.nil = self.element.attr("data-nil") || self.nil || "â"; 164 self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null; 165 self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs; 166 self.original_content = self.element.attr("data-original-content") || self.original_content; 167 self.collectionValue = self.element.attr("data-value") || self.collectionValue; 168 169 if (!self.element.attr("data-sanitize")) { 170 self.sanitize = true; 171 } 172 else { 173 self.sanitize = (self.element.attr("data-sanitize") == "true"); 174 } 175 176 if (!self.element.attr("data-use-confirm")) { 177 self.useConfirm = true; 178 } else { 179 self.useConfirm = (self.element.attr("data-use-confirm") != "false"); 180 } 181 182 if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null) 183 { 184 self.values = jQuery.parseJSON(self.collection); 185 } 186 187 }, 188 189 bindForm : function() { 190 this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm; 191 this.getValue = BestInPlaceEditor.forms[this.formType].getValue; 192 }, 193 194 initNil: function() { 195 if (this.element.html() === "") 196 { 197 this.element.html(this.nil); 198 } 199 }, 200 201 isNil: function() { 202 // TODO: It only work when form is deactivated. 203 // Condition will fail when form is activated 204 return this.element.html() === "" || this.element.html() === this.nil; 205 }, 206 207 getValue : function() { 208 alert("The form was not properly initialized. getValue is unbound"); 209 }, 210 211 // Trim and Strips HTML from text
212 sanitizeValue : function(s) { 213 return jQuery.trim(s); 214 }, 215 216 /* Generate the data sent in the POST request */ 217 requestData : function() { 218 // To prevent xss attacks, a csrf token must be defined as a meta attribute 219 csrf_token = jQuery('meta[name=csrf-token]').attr('content'); 220 csrf_param = jQuery('meta[name=csrf-param]').attr('content'); 221 222 var data = "_method=put"; 223 data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue()); 224 225 if (csrf_param !== undefined && csrf_token !== undefined) { 226 data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token); 227 } 228 return data; 229 }, 230 231 ajax : function(options) { 232 options.url = this.url; 233 options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); }; 234 return jQuery.ajax(options); 235 }, 236 237 // Handlers //////////////////////////////////////////////////////////////// 238 239 loadSuccessCallback : function(data) { 240 data = jQuery.trim(data); 241 242 if(data && data!=""){ 243 var response = jQuery.parseJSON(jQuery.trim(data)); 244 if (response !== null && response.hasOwnProperty("display_as")) { 245 this.element.attr("data-original-content", this.element.text()); 246 this.original_content = this.element.text(); 247 this.element.html(response["display_as"]); 248 } 249 250 this.element.trigger(jQuery.Event("best_in_place:success"), data); 251 this.element.trigger(jQuery.Event("ajax:success"), data); 252 } else { 253 this.element.trigger(jQuery.Event("best_in_place:success")); 254 this.element.trigger(jQuery.Event("ajax:success")); 255 } 256 257 // Binding back after being clicked 258 jQuery(this.activator).bind('click', {editor: this}, this.clickHandler); 259 this.element.trigger(jQuery.Event("best_in_place:deactivate")); 260 261 if (this.collectionValue !== null && this.formType == "select") { 262 this.collectionValue = this.previousCollectionValue; 263 this.previousCollectionValue = null; 264 } 265 }, 266 267 loadErrorCallback : function(request, error) { 268 this.activateText(this.oldValue); 269 270 this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]); 271 this.element.trigger(jQuery.Event("ajax:error"), request, error); 272 273 // Binding back after being clicked 274 jQuery(this.activator).bind('click', {editor: this}, this.clickHandler); 275 this.element.trigger(jQuery.Event("best_in_place:deactivate")); 276 }, 277 278 clickHandler : function(event) { 279 event.preventDefault(); 280 event.data.editor.activate(); 281 }, 282 283 setHtmlAttributes : function() { 284 var formField = this.element.find(this.formType); 285 286 if(this.html_attrs){ 287 var attrs = jQuery.parseJSON(this.html_attrs); 288 for(var key in attrs){ 289 formField.attr(key, attrs[key]); 290 } 291 } 292 } 293}; 294 295 296// Button cases: 297// If no buttons, then blur saves, ESC cancels 298// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!) 299// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels 300// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels 301BestInPlaceEditor.forms = { 302 "input" : { 303 activateForm : function() { 304 var output = jQuery(document.createElement('form')) 305 .addClass('form_in_place') 306 .attr('action', 'javascript:void(0);') 307 .attr('style', 'display:inline'); 308 var input_elt = jQuery(document.createElement('input')) 309 .attr('type', 'text') 310 .attr('name', this.attributeName) 311 .val(this.display_value); 312 if(this.inner_class !== null) { 313 input_elt.addClass(this.inner_class); 314 } 315 output.append(input_elt); 316 if(this.okButton) { 317 output.append( 318 jQuery(document.createElement('input')) 319 .attr('type', 'submit') 320 .attr('class', this.okButtonClass) 321 .attr('value', this.okButton) 322 ) 323 } 324 if(this.cancelButton) { 325 output.append( 326 jQuery(document.createElement('input')) 327 .attr('type', 'button') 328 .attr('class', this.cancelButtonClass) 329 .attr('value', this.cancelButton) 330 ) 331 } 332 333 this.element.html(output); 334 this.setHtmlAttributes(); 335 this.element.find("input[type='text']")[0].select(); 336 this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler); 337 if (this.cancelButton) { 338 this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler); 339 } 340 this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler); 341 this.element.find("input[type='text']").bind('keyup', {editor: this}
341, BestInPlaceEditor.forms.input.keyupHandler); 342 this.blurTimer = null; 343 this.userClicked = false; 344 }, 345 346 getValue : function() { 347 return this.sanitizeValue(this.element.find("input").val()); 348 }, 349 350 // When buttons are present, use a timer on the blur event to give precedence to clicks 351 inputBlurHandler : function(event) { 352 if (event.data.editor.okButton) { 353 event.data.editor.blurTimer = setTimeout(function () { 354 if (!event.data.editor.userClicked) { 355 event.data.editor.abort(); 356 } 357 }, 500); 358 } else { 359 if (event.data.editor.cancelButton) { 360 event.data.editor.blurTimer = setTimeout(function () { 361 if (!event.data.editor.userClicked) { 362 event.data.editor.update(); 363 } 364 }, 500); 365 } else { 366 event.data.editor.update(); 367 } 368 } 369 }, 370 371 submitHandler : function(event) { 372 event.data.editor.userClicked = true; 373 clearTimeout(event.data.editor.blurTimer); 374 event.data.editor.update(); 375 }, 376 377 cancelButtonHandler : function(event) { 378 event.data.editor.userClicked = true; 379 clearTimeout(event.data.editor.blurTimer); 380 event.data.editor.abort(); 381 event.stopPropagation(); // Without this, click isn't handled 382 }, 383 384 keyupHandler : function(event) { 385 if (event.keyCode == 27) { 386 event.data.editor.abort(); 387 } 388 } 389 }, 390 391 "date" : { 392 activateForm : function() { 393 var that = this, 394 output = jQuery(document.createElement('form')) 395 .addClass('form_in_place') 396 .attr('action', 'javascript:void(0);') 397 .attr('style', 'display:inline'), 398 input_elt = jQuery(document.createElement('input')) 399 .attr('type', 'text') 400 .attr('name', this.attributeName) 401 .attr('value', this.sanitizeValue(this.display_value)); 402 if(this.inner_class !== null) { 403 input_elt.addClass(this.inner_class); 404 } 405 output.append(input_elt) 406 407 this.element.html(output); 408 this.setHtmlAttributes(); 409 this.element.find('input')[0].select(); 410 this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler); 411 this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler); 412 413 this.element.find('input') 414 .datepicker({ 415 onClose: function() { 416 that.update(); 417 } 418 }) 419 .datepicker('show'); 420 }, 421 422 getValue : function() { 423 return this.sanitizeValue(this.element.find("input").val()); 424 }, 425 426 submitHandler : function(event) { 427 event.data.editor.update(); 428 }, 429 430 keyupHandler : function(event) { 431 if (event.keyCode == 27) { 432 event.data.editor.abort(); 433 } 434 } 435 }, 436 437 "select" : { 438 activateForm : function() { 439 var output = jQuery(document.createElement('form')) 440 .attr('action', 'javascript:void(0)') 441 .attr('style', 'display:inline'); 442 selected = '', 443 oldValue = this.oldValue, 444 select_elt = jQuery(document.createElement('select')) 445 .attr('class', this.inned_class !== null ? this.inner_class : '' ), 446 currentCollectionValue = this.collectionValue; 447 448 jQuery.each(this.values, function (index, value) { 449 var option_elt = jQuery(document.createElement('option')) 450 // .attr('value', value[0]) 451 .val(value[0]) 452 .html(value[1]); 453 if(value[0] == currentCollectionValue) { 454 option_elt.attr('selected', 'selected'); 455 } 456 select_elt.append(option_elt); 457 }); 458 output.append(select_elt); 459 460 this.element.html(output); 461 this.setHtmlAttributes(); 462 this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler); 463 this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler); 464 this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler); 465 this.element.find("select")[0].focus(); 466 }, 467 468 getValue : function() {
469 return this.sanitizeValue(this.element.find("select").val()); 470 // return this.element.find("select").val(); 471 }, 472 473 blurHandler : function(event) { 474 event.data.editor.update(); 475 }, 476 477 keyupHandler : function(event) { 478 if (event.keyCode == 27) event.data.editor.abort(); 479 } 480 }, 481 482 "checkbox" : { 483 activateForm : function() { 484 this.collectionValue = !this.getValue(); 485 this.setHtmlAttributes(); 486 this.update(); 487 }, 488 489 getValue : function() { 490 return this.collectionValue; 491 } 492 }, 493 494 "textarea" : { 495 activateForm : function() { 496 // grab width and height of text 497 width = this.element.css('width'); 498 height = this.element.css('height'); 499 500 // construct form 501 var output = jQuery(document.createElement('form')) 502 .attr('action', 'javascript:void(0)') 503 .attr('style', 'display:inline') 504 .append(jQuery(document.createElement('textarea')) 505 .val(this.sanitizeValue(this.display_value))); 506 if(this.okButton) { 507 output.append( 508 jQuery(document.createElement('input')) 509 .attr('type', 'submit') 510 .attr('value', this.okButton) 511 ); 512 } 513 if(this.cancelButton) { 514 output.append( 515 jQuery(document.createElement('input')) 516 .attr('type', 'button') 517 .attr('value', this.cancelButton) 518 ) 519 } 520 521 this.element.html(output); 522 this.setHtmlAttributes(); 523 524 // set width and height of textarea 525 jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height }); 526 jQuery(this.element.find("textarea")[0]).elastic(); 527 528 this.element.find("textarea")[0].focus(); 529 this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler); 530 if (this.cancelButton) { 531 this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler); 532 } 533 this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler); 534 this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler); 535 this.blurTimer = null; 536 this.userClicked = false; 537 }, 538 539 getValue : function() { 540 return this.sanitizeValue(this.element.find("textarea").val()); 541 }, 542 543 // When buttons are present, use a timer on the blur event to give precedence to clicks 544 blurHandler : function(event) { 545 if (event.data.editor.okButton) { 546 event.data.editor.blurTimer = setTimeout(function () { 547 if (!event.data.editor.userClicked) { 548 event.data.editor.abortIfConfirm(); 549 } 550 }, 500); 551 } else { 552 if (event.data.editor.cancelButton) { 553 event.data.editor.blurTimer = setTimeout(function () { 554 if (!event.data.editor.userClicked) { 555 event.data.editor.update(); 556 } 557 }, 500); 558 } else { 559 event.data.editor.update(); 560 } 561 } 562 }, 563 564 submitHandler : function(event) { 565 event.data.editor.userClicked = true; 566 clearTimeout(event.data.editor.blurTimer); 567 event.data.editor.update(); 568 }, 569 570 cancelButtonHandler : function(event) { 571 event.data.editor.userClicked = true; 572 clearTimeout(event.data.editor.blurTimer); 573 event.data.editor.abortIfConfirm(); 574 event.stopPropagation(); // Without this, click isn't handled 575 }, 576 577 keyupHandler : function(event) { 578 if (event.keyCode == 27) { 579 event.data.editor.abortIfConfirm(); 580 } 581 } 582 } 583}; 584 585jQuery.fn.best_in_place = function() { 586 587 function setBestInPlace(element) { 588 if (!element.data('bestInPlaceEditor')) { 589 element.data('bestInPlaceEditor', new BestInPlaceEditor(element)); 590 return true; 591 } 592 } 593 594 jQuery(this.context).delegate(this.selector, 'click', function () { 595 var el = jQuery(this); 596 if (setBestInPlace(el)) 597 el.click(); 598 }); 599 600 this.each(function () { 601 setBestInPlace(jQuery(this)); 602 }); 603 604 return this; 605}; 606 607 608 609/** 610* @name Elastic 611* @descripton Elastic is Jquery plugin that grow and shrink y
611our textareas automaticliy 612* @version 1.6.5 613* @requires Jquery 1.2.6+ 614* 615* @author Jan Jarfalk 616* @author-email [email protected] 617* @author-website http://www.unwrongest.com 618* 619* @licens MIT License - http://www.opensource.org/licenses/mit-license.php 620*/ 621 622(function(jQuery){ 623 if (typeof jQuery.fn.elastic !== 'undefined') return; 624 625 jQuery.fn.extend({ 626 elastic: function() { 627 // We will create a div clone of the textarea 628 // by copying these attributes from the textarea to the div. 629 var mimics = [ 630 'paddingTop', 631 'paddingRight', 632 'paddingBottom', 633 'paddingLeft', 634 'fontSize', 635 'lineHeight', 636 'fontFamily', 637 'width', 638 'fontWeight']; 639 640 return this.each( function() { 641 642 // Elastic only works on textareas 643 if ( this.type != 'textarea' ) { 644 return false; 645 } 646 647 var $textarea = jQuery(this), 648 $twin = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}), 649 lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'), 650 minheight = parseInt($textarea.css('height'),10) || lineHeight*3, 651 maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE, 652 goalheight = 0, 653 i = 0; 654 655 // Opera returns max-height of -1 if not set 656 if (maxheight < 0) { maxheight = Number.MAX_VALUE; } 657 658 // Append the twin to the DOM 659 // We are going to meassure the height of this, not the textarea. 660 $twin.appendTo($textarea.parent()); 661 662 // Copy the essential styles (mimics) from the textarea to the twin 663 i = mimics.length; 664 while(i--){ 665 $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString())); 666 } 667 668 669 // Sets a given height and overflow state on the textarea 670 function setHeightAndOverflow(height, overflow){ 671 curratedHeight = Math.floor(parseInt(height,10)); 672 if($textarea.height() != curratedHeight){ 673 $textarea.css({'height': curratedHeight + 'px','overflow':overflow}); 674 675 } 676 } 677 678 679 // This function will update the height of the textarea if necessary 680 function update() { 681 682 // Get curated content from the textarea. 683 var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />'); 684 685 // Compare curated content with curated twin. 686 var twinContent = $twin.html().replace(/<br>/ig,'<br />'); 687 688 if(textareaContent+' ' != twinContent){ 689 690 // Add an extra white space so new rows are added when you are at the end of a row. 691 $twin.html(textareaContent+' '); 692 693 // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height 694 if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){ 695 696 var goalheight = $twin.height()+lineHeight; 697 if(goalheight >= maxheight) { 698 setHeightAndOverflow(maxheight,'auto'); 699 } else if(goalheight <= minheight) { 700 setHeightAndOverflow(minheight,'hidden'); 701 } else { 702 setHeightAndOverflow(goalheight,'hidden'); 703 } 704 705 } 706 707 } 708 709 } 710 711 // Hide scrollbars 712 $textarea.css({'overflow':'hidden'}); 713 714 // Update textarea size on keyup, change, cut and paste 715 $textarea.bind('keyup change cut paste', function(){ 716 update(); 717 }); 718 719 // Compact textarea on blur 720 // Lets animate this.... 721 $textarea.bind('blur',function(){ 722 if($twin.height() < maxheight){ 723 if($twin.height() > minheight) { 724 $textarea.height($twin.height()); 725 } else { 726 $textarea.height(minheight); 727 } 728 } 729 }); 730 731 // And this line is to catch the browser paste event 732 $textarea.on("input paste", function(e){ setTimeout( update, 250); }); 733 734 // Run update once when elastic is initialized 735 update(); 736 737 }); 738 739 } 740 }); 741})(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.