Get last line from a textarea on keypress - javascript

I have a textarea field and on every keypress, I would like to push the last line in the textarea to an array.
Currently, I am constructing the array on every keypress to get the last line in the textarea. Is there a way to optimize this? Meaning, get last line in the textarea without having to construct an array.
jQuery('#mytextarea').keypress(function() {
lines = jQuery('#mytextarea').text().split("\n");
lastLine = lines[lines.length - 1];
});
if(.. some condition ..) {
myArray.push(lastLine);

Indeed, there is a way to optimize this. The optimization is mostly memory usage - the actual CPU usage is also improved.
The optimized version relies on lastIndexOf(). It is as follows:
jQuery("#mytextarea").keypress(function() {
var content = this.value;
var lastLine = content.substr(content.lastIndexOf("\n")+1);
});
You will notice a couple of micro-optimizations:
this already is the DOM element. There is little point in re-invoking jQuery just to get the text content. Saves a bit on processor
using lastIndexOf allows me to get anything after the last \n
Dogbert provided a benchmark for the lastIndexOf: http://jsperf.com/splitting-large-strings

Related

Chrome extension - The new >>Manifest_version: 3<< Problem [duplicate]

Can the JavaScript command .replace replace text in any webpage? I want to create a Chrome extension that replaces specific words in any webpage to say something else (example cake instead of pie).
The .replace method is a string operation, so it's not immediately simple to run the operation on HTML documents, which are composed of DOM Node objects.
Use TreeWalker API
The best way to go through every node in a DOM and replace text in it is to use the document.createTreeWalker method to create a TreeWalker object. This is a practice that is used in a number of Chrome extensions!
// create a TreeWalker of all text nodes
var allTextNodes = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT),
// some temp references for performance
tmptxt,
tmpnode,
// compile the RE and cache the replace string, for performance
cakeRE = /cake/g,
replaceValue = "pie";
// iterate through all text nodes
while (allTextNodes.nextNode()) {
tmpnode = allTextNodes.currentNode;
tmptxt = tmpnode.nodeValue;
tmpnode.nodeValue = tmptxt.replace(cakeRE, replaceValue);
}
To replace parts of text with another element or to add an element in the middle of text, use DOM splitText, createElement, and insertBefore methods, example.
See also how to replace multiple strings with multiple other strings.
Don't use innerHTML or innerText or jQuery .html()
// the innerHTML property of any DOM node is a string
document.body.innerHTML = document.body.innerHTML.replace(/cake/g,'pie')
It's generally slower (especially on mobile devices).
It effectively removes and replaces the entire DOM, which is not awesome and could have some side effects: it destroys all event listeners attached in JavaScript code (via addEventListener or .onxxxx properties) thus breaking the functionality partially/completely.
This is, however, a common, quick, and very dirty way to do it.
Ok, so the createTreeWalker method is the RIGHT way of doing this and it's a good way. I unfortunately needed to do this to support IE8 which does not support document.createTreeWalker. Sad Ian is sad.
If you want to do this with a .replace on the page text using a non-standard innerHTML call like a naughty child, you need to be careful because it WILL replace text inside a tag, leading to XSS vulnerabilities and general destruction of your page.
What you need to do is only replace text OUTSIDE of tag, which I matched with:
var search_re = new RegExp("(?:>[^<]*)(" + stringToReplace + ")(?:[^>]*<)", "gi");
gross, isn't it. you may want to mitigate any slowness by replacing some results and then sticking the rest in a setTimeout call like so:
// replace some chunk of stuff, the first section of your page works nicely
// if you happen to have that organization
//
setTimeout(function() { /* replace the rest */ }, 10);
which will return immediately after replacing the first chunk, letting your page continue with its happy life. for your replace calls, you're also going to want to replace large chunks in a temp string
var tmp = element.innerHTML.replace(search_re, whatever);
/* more replace calls, maybe this is in a for loop, i don't know what you're doing */
element.innerHTML = tmp;
so as to minimize reflows (when the page recalculates positioning and re-renders everything). for large pages, this can be slow unless you're careful, hence the optimization pointers. again, don't do this unless you absolutely need to. use the createTreeWalker method zetlen has kindly posted above..
have you tryed something like that?
$('body').html($('body').html().replace('pie','cake'));

How to remove empty lines in a google doc using the script editor (Google Docs add-ons)?

I am creating a Google Docs add-on, and one of the features I am trying to create is a feature that reduces the number of empty lines between two paragraphs.
So, for instance, if I have 2 paragraphs with 5 empty/blank lines between them, I want the feature to reduce the number of empty lines to 1.
Essentially, I need a way to detect an empty line. I've looked at the API, and think I need to use the replaceText() method which searches for a regex pattern. However, I've tried it, and it didn't work (perhaps I'm using an incorrect patter, i dont know).
Can anyone help me in finding a way to detect an empty line? Thanks.
EDIT:
I just found out that Google Docs don't support all patterns of regex. Here is that link: https://support.google.com/analytics/answer/1034324?hl=en . I'm not familiar with regex. Can anyone provide an alternative that works in Google Docs.
Written and tested the below function, here are the steps to make it work
Copy the text from here Lorem Ipsum Google Doc, to a new Google Doc
Add the below snippet to your Tools > Script editor > project
Comments inline
// Regular expressions with the following special characters are not supported,
// as they can cause delays in processing your email: * (asterisk), + (plus sign)
// So RegEx is not applicable since you can't use "\s*", we need to find another solution
// A row/line is a Paragraph, weird right? So now you iterate through the rows
// Trim paragraph (row), if it's empty, then you can delete it.
function removeEmptyLines() {
var doc = DocumentApp.getActiveDocument();
Logger.log("Before:\n", doc.getBody().getText());
var paragraphs = doc.getBody().getParagraphs();
// Iterating from the last paragraph to the first
for (var i=paragraphs.length-1; i>=0; i--){
var line = paragraphs[i];
if ( ! line.getText().trim() ) {
// Paragraph (line) is empty, remove it
line.removeFromParent()
Logger.log("Removed: ", i);
}
}
Logger.log("After:\n", doc.getBody().getText());
}
References
https://developers.google.com/apps-script/reference/document/text#replaceText(String,String)
https://support.google.com/a/answer/1371415?hl=en
https://stackoverflow.com/a/3012832/5285732

jQuery, how to test of a variable is a text node, containing no markup?

http://jsfiddle.net/DerNalia/zrppg/8/
I have two lines of code that pretty much do the same thing
var doesntbreak = $j("hello");
var breaks = $j(" ");
​The first one doesn't error, but the second one throws this
Syntax error, unrecognized expression:
should'nt they both behave the same?
any insight as to how to solve this?
in the actual method I'm using, ele is from the Dom, so it could eb a text node, or any other kind of node.
UPDATE:
the input to the function that I'm using that I noticed this takes selection from the dom.
updated example: http://jsfiddle.net/DerNalia/zrppg/11/ <- includes html markup.
So, I guess, my question is, how do I test if something is JUST a text node? and doesn't contain any markup?
In general, you cannot create standalone text nodes with the jQuery function. If a string isn't obviously HTML, it gets treated as a selector, and is not recognized by jQuery as a valid selector.
Assuming you want to parse arbitrary strings (which may have HTML tags or not), I suggest something like var result = $('<div></div>').html(' ').contents();. Place your your HTML or text string in a div to parse it and then immediately extract the parsed result as a jQuery object with the list of elements. You can append the resultant list of elements with $(parentElem).append(result);
try this:
function isTextNode(node){
div=document.createElement('div');
div.innerHTML=node;
return $(div).text()==$(div).html();
}
And " " is'nt a valid selector if you want to find a elements containing some text you must use the :contains selector http://api.jquery.com/contains-selector/
Internet Explorer (older versions at least) don't have built in "querySelector" functions, so the Sizzle engine has to do the work directly. Thus, the slightly different tolerances for bogus input can cause differences in error reporting.
Your selector expression " " is equally invalid in all browsers, however. The library is not obliged to quietly accept anything you pass it, so perhaps you should reconsider your application design.
If you want to check for entities, you could use a regular expression if you're confident that it's just a text node. Or you could get the contents with .text() instead of .html().
So, I have to thank Apsillers and Rolando for pointing me in the right direction. Their answers were very close, but gave me the information I needed.
This is what I ended up using:
TEXT_NODE = 3;
objectify = function(n) {
return $j("<div></div>").html(n).contents();
}
function textOnly(n) {
var o = objectify(n);
for (var i = 0; i < o.length; i++) {
if (objectify(o[i])[0].nodeType != TEXT_NODE) {
return false
}
}
return true;
}
And here is a jsFiddle with some test cases, that neither of the original code submissions passed.
to pass, it needed to handle this kind of input
"hello" // true
"hello<b>there</b>" // false
"<b>there</b>" // false
" " // false
Not actual answer, but may help someone with similar issue as mine and loosely related to this question. :)
I was getting same issue today, so fixed by removing
Changed:
var breaks = $j(" ");
to:
var breaks = $j(" ".replace(/&.*;/g, ""));
Here I am removing , < etc...
Note: value at is dynamic for me, so it can be anything.

Caret position not keeping track?

What I'm trying to do is put key codes in an array, to do some interesting stuff with later on. So, I capture the keystrokes, get the caret position and put the key code in the array (with some help from MooTools):
var keyArray = [];
$('form').addEvent('keyup', function(event) {
var pos = document.activeElement.getCaretPosition();
keyArray[pos] = event.code;
});
Generally speaking, this works great. However, I noticed some undefined values in my array when showing the complete array in my console. Exploring this further, I found out that when typing quickly, the caret position seems to lose track, or being quick/slow to respond. I've made a jsfiddle to demonstrate this: http://jsfiddle.net/HQVR8/1/
If you type quickly in this example, you'll see a caret position sequence like
- 1 - 2 - 3 - 5 - 6 - 6.
But when typing slowly, it's
- 1 - 2 - 3 - 4 - 5 - 6.
Of course, the trouble now when typing quickly is that I have a undefined value in my array and I overwrite one array item. So the result is different.
My question is if I can somehow let the caret position keep track. I've tried using the 'native' selectionStart instead, but results are the same. I also tried capturing the caret position in a keydown event and putting it in an array in the keyup event. No difference. I'm wondering if using little pauses (ie. forcing the user to type slower) might solve it, but this feels like a hack and I would prefer a 'proper' solution. Hopefully, there is one.
you are basically making a mess by using an array instead of an object.
array indexes are dodgy and can create sparse arrays if you are not careful. for instance:
var foo = [];
foo[0] = "one!"; // foo.length = 1;
foo[2] = "two!"; // foo.length = 3, foo[1] = undefined
so if you type too fast and skip the return value, it is probably doing just that. also, pasting etc can push caret further down...
you can probably use an object though there are no guarantees on the order of keys in vs keys out in all browsers - particularly webkit, who tend to reorder and put numeric keys at top of object key loops... but if you prefix the key like "pos" + caretIndex, you ought to get FIFO
one way to solve your need to extract the actual codes w/o the undefined is via Array.filter.
eg.
retArray = Array.filter(retArray, function(el) {
return el !== undefined;
});
With an object, you can just do:
console.log(Object.values(foo));
After some more tests, it appears to be a behavior specific to keyup. When I use keydown I do get a consistent sequence: http://jsfiddle.net/HQVR8/3/
One disadvantage is that keydown is a step behind keyup when you're doing the 'value collecting' I'm doing, but in my setting, this is only a minor issue.
The difference in behavior appears odd to me: when you press four keys at a time, the keyup displays the same caret position for all of them, while keydown is showing four separate caret positions. This seems odd because you press them at once, but also depress them at once, so the caret 'readings' should be the same.
go to the JSfiddle and fiddle with this:
a) depress 'q', then depress 'w', then release 'q', then release 'w' (fast enough to avoid autorepeat). That kind of sequence happens quite often when you type 'quickly' with more than one finger :).
b) leave any key depressed long enough for the autorepeat to kick in.
The differences are in plain view
Basically, keyUp fires when the physical key is released, but
another key can be depressed before the previous one is released
keyDown is called in case of autorepeat, while keyUp isn't
Since carret position and whatever text area updates are kept in sync with keyDown, you will simply miss the intermediate steps when monitoring keyUp.
In case (a), the keyUp handler sees carret pos jump from 0 to 2 as 'q' is released because two characters have been typed before the first key was released. The release of 'w' does not change carret position, so the handler ends up storing both q and w at position 2.
In case (b), the handler misses the repeats completely, and stores only the last instance of the key.
As a conclusion, using KeyUp will never achieve the intended meaning.
keyDown could, but personnally I would rather hook the changed event.
I reckon it is no less reliable to detect a bot filling the field (after all, a legit user could want to paste the password too), and you won't have to bother with control keys like backspace or whatever other non-keyboard means of clearing the input.
You could use keyUp as an extra paranoid check, as long as you don't expect to reconstruct the exact input. For instance, you could simply check that the released key matches the character at current cursor position. But frankly I'm not sure it is worth the bother.

Will the onCopy event help me repair the bullet hole in my foot?

In a huge table of numbers, I made the user's experience "richer" by replacing all of the semi-visible minus signs by –. It looks great, big improvement. I was so busy admiring my cleverness that I forgot to notice the blood on the floor.
Because, come to find out, when the guy goes to select, copy, and then paste (elsewhere) such transformed minus signs, guess what? they're not minus signs any more.
Can I use the onCopy event dependably, straightforwardly, and cross-browserly (including Mac browsers) to change those – chars back to minus signs in the matter that was (or is about to be) copied?
If so, have you any hints on doing it?
EDIT: I'm using native JavaScript, not using any framework.
Thanks!
I don't believe there's a way JavaScript can manipulate what is inside the clipboard because that's an OS feature. What you could do I believe is manipulate the text after the user pastes it into a field of your choosing. Here's an example with JQuery:
$('#my_text_field').bind('paste',function()
{
$(this).val($(this).val().replace('–','-'));
}
The – character will copy/paste correctly if inserted programmatically using javascript (and if you insert the actual character, instead of the HTML entity).
Perhaps you could replace every – that you have with something like:
<span class="fancyDash"></span>
And then on load you can run something like:
var longDash = '\u2013';
jQuery.each($(".fancyDash"), function() {
this.innerHTML = longDash;
});
Here is a working example: http://jsfiddle.net/m9fhS/
Edit:
Or, if not using jQuery, you can first patch up document.getElementsByClassName so that it works correctly for anyone using IE, and then do:
var longDash = '\u2013';
var spans = document.getElementsByClassName("fancyDash");
for (var index = 0; index < spans.length; index++) {
spans[index].innerHTML = longDash;
}
As shown here: http://jsfiddle.net/m9fhS/1/

Categories

Resources