I have a code element, and I know the text I'm looking for is inside it, for example:
<p>
Lorem ipsum <span class="bold">dolor</span> sit amet
</p>
Note the span that is used for styling specific words.
Now, assume I have a reference to the p element, and I want to programmatically mark the ipsum dolor sit part, how can achieve that?
You can use the Selection API with a Range argument to programmatically select text in an element.
The Range start and end positions accept a Child Node number, or Character inside a Text node. In our case, we need to reach the Text nodes to direct to the text position inside them (in our example, it will start on the first Text node of p, in position 11, and will end on the last Text in position 4).
To find the right node and the text position inside it, use the next function:
const findPositionInsideTree = (node, position) => {
if (node.nodeType === Node.TEXT_NODE) {
return { node, position };
}
for (let child of node.childNodes) {
if (position <= child.textContent.length) return findPositionInsideTree(child, position);
position -= child.textContent.length;
}
};
This recursive code loops over the child nodes and counts the expected position inside each node.
And now you only need to call this function for your text, create a Range and add it to the Selection:
const textStart = element.textContent.indexOf('ipsum dolor sit');
const textEnd = textStart + 'ipsum dolor sit'.length;
const start = findPositionInsideTree(element, textStart);
const end = findPositionInsideTree(element, textEnd);
const range = new Range();
range.setStart(start.node, start.position);
range.setEnd(end.node, end.position);
window.getSelection().removeAllRanges()
window.getSelection().addRange(range)
Maybe you can use this:
const text = pElement.textContent;
const words = text.split(" ");
const startIndex = words.indexOf("ipsum");
const spanElement = document.createElement("span");
spanElement.classList.add("bold");
spanElement.textContent = words.slice(startIndex, startIndex + 3).join(" ");
pElement.innerHTML = words.slice(0, startIndex).join(" ") + spanElement.outerHTML + words.slice(startIndex + 3).join(" ");
Related
This is start of a srt file:
0
00:00:07,000 --> 00:01:00,000
lorem ipsum... // this line doesn't work
1
00:01:02,960 --> 00:01:05,800
lorem ipsum...
2
00:01:05,840 --> 00:01:08,960
lorem ipsum...
The first line doesn't work, I suppose because of0 as the ordinal.
I need a way, javascript way if possible, to correctly change all ordinal numbers at once, not one by one (over 1000 lines), starting with 1 and not with 0.
I was searching for various online solutions, without success.
You could create an html file with a textarea, load it in your browser and copy
the contents of the .srt file in. Use the following javascript to convert the textarea's text:
var numberRegex = /^\d\s*$/;
var originalLines = text.split('\n');
var lines = [];
for (var index = 0; index != originalLines.length; ++index)
{
var orig = originalLines[index];
var match = numberRegex.exec(orig);
lines.push(match ? parseInt(orig) + 1 : orig);
}
Then the converted text you want is provided by:
lines.join('\n')
I have a challenging problem to solve. I'm working on a script which takes a regex as an input. This script then finds all matches for this regex in a document and wraps each match in its own <span> element. The hard part is that the text is a formatted html document, so my script needs to navigate through the DOM and apply the regex across multiple text nodes at once, while figuring out where it has to split text nodes if needed.
For example, with a regex that captures full sentences starting with a capital letter and ending with a period, this document:
<p>
<b>HTML</b> is a language used to make <b>websites.</b>
It was developed by <i>CERN</i> employees in the early 90s.
</p>
Would ideally be turned into this:
<p>
<span><b>HTML</b> is a language used to make <b>websites.</b></span>
<span>It was developed by <i>CERN</i> employees in the early 90s.</span>
</p>
The script should then return the list of all created spans.
I already have some code which finds all the text nodes and stores them in a list along with their position across the whole document and their depth. You don't really need to understand that code to help me and its recursive structure can be a bit confusing. The first part I'm not sure how to do is figure out which elements should be included within the span.
function findTextNodes(node, depth = -1, start = 0) {
let list = [];
if (node.nodeType === Node.TEXT_NODE) {
list.push({ node, depth, start });
} else {
for (let i = 0; i < node.childNodes.length; ++i) {
list = list.concat(findTextNodes(node.childNodes[i], depth+1, start));
if (list.length) {
start += list[list.length-1].node.nodeValue.length;
}
}
}
return list;
}
I figure I'll make a string out of all the document, run the regex through it and use the list to find which nodes correspond to witch regex matches and then split the text nodes accordingly.
But an issue arrives when I have a document like this:
<p>
This program is not stable yet. Do not use this in production yet.
</p>
There's a sentence which starts outside of the <a> tag but ends inside it. Now I don't want the script to split that link in two tags. In a more complex document, it could ruin the page if it did. The code could either wrap two sentences together:
<p>
<span>This program is not stable yet. Do not use this in production yet.</span>
</p>
Or just wrap each part in its own element:
<p>
<span>This program is </span>
<a href="beta.html">
<span>not stable yet.</span>
<span>Do not use this in production yet.</span>
</a>
</p>
There could be a parameter to specify what it should do. I'm just not sure how to figure out when an impossible cut is about to happen, and how to recover from it.
Another issue comes when I have whitespace inside a child element like this:
<p>This is a <b>sentence. </b></p>
Technically, the regex match would end right after the period, before the end of the <b> tag. However, it would be much better to consider the space as part of the match and wrap it like this:
<p><span>This is a <b>sentence. </b></span></p>
Than this:
<p><span>This is a </span><b><span>sentence.</span> </b></p>
But that's a minor issue. After all, I could just allow extra white-space to be included within the regex.
I know this might sound like a "do it for me" question and its not the kind of quick question we see on SO on a daily basis, but I've been stuck on this for a while and it's for an open-source library I'm working on. Solving this problem is the last obstacle. If you think another SE site is best suited for this question, redirect me please.
Here are two ways to deal with this.
I don't know if the following will exactly match your needs. It's a simple enough solution to the problem, but at least it doesn't use RegEx to manipulate HTML tags. It performs pattern matching against the raw text and then uses the DOM to manipulate the content.
First approach
This approach creates only one <span> tag per match, leveraging some less common browser APIs.
(See the main problem of this approach below the demo, and if not sure, use the second approach).
The Range class represents a text fragment. It has a surroundContents function that lets you wrap a range in an element. Except it has a caveat:
This method is nearly equivalent to newNode.appendChild(range.extractContents()); range.insertNode(newNode). After surrounding, the boundary points of the range include newNode.
An exception will be thrown, however, if the Range splits a non-Text node with only one of its boundary points. That is, unlike the alternative above, if there are partially selected nodes, they will not be cloned and instead the operation will fail.
Well, the workaround is provided in the MDN, so all's good.
So here's an algorithm:
Make a list of Text nodes and keep their start indices in the text
Concatenate these nodes' values to get the text
Find matches over the text, and for each match:
Find the start and end nodes of the match, comparing the the nodes' start indices to the match position
Create a Range over the match
Let the browser do the dirty work using the trick above
Rebuild the node list since the last action changed the DOM
Here's my implementation with a demo:
function highlight(element, regex) {
var document = element.ownerDocument;
var getNodes = function() {
var nodes = [],
offset = 0,
node,
nodeIterator = document.createNodeIterator(element, NodeFilter.SHOW_TEXT, null, false);
while (node = nodeIterator.nextNode()) {
nodes.push({
textNode: node,
start: offset,
length: node.nodeValue.length
});
offset += node.nodeValue.length
}
return nodes;
}
var nodes = getNodes(nodes);
if (!nodes.length)
return;
var text = "";
for (var i = 0; i < nodes.length; ++i)
text += nodes[i].textNode.nodeValue;
var match;
while (match = regex.exec(text)) {
// Prevent empty matches causing infinite loops
if (!match[0].length)
{
regex.lastIndex++;
continue;
}
// Find the start and end text node
var startNode = null, endNode = null;
for (i = 0; i < nodes.length; ++i) {
var node = nodes[i];
if (node.start + node.length <= match.index)
continue;
if (!startNode)
startNode = node;
if (node.start + node.length >= match.index + match[0].length)
{
endNode = node;
break;
}
}
var range = document.createRange();
range.setStart(startNode.textNode, match.index - startNode.start);
range.setEnd(endNode.textNode, match.index + match[0].length - endNode.start);
var spanNode = document.createElement("span");
spanNode.className = "highlight";
spanNode.appendChild(range.extractContents());
range.insertNode(spanNode);
nodes = getNodes();
}
}
// Test code
var testDiv = document.getElementById("test-cases");
var originalHtml = testDiv.innerHTML;
function test() {
testDiv.innerHTML = originalHtml;
try {
var regex = new RegExp(document.getElementById("regex").value, "g");
highlight(testDiv, regex);
}
catch(e) {
testDiv.innerText = e;
}
}
document.getElementById("runBtn").onclick = test;
test();
.highlight {
background-color: yellow;
border: 1px solid orange;
border-radius: 5px;
}
.section {
border: 1px solid gray;
padding: 10px;
margin: 10px;
}
<form class="section">
RegEx: <input id="regex" type="text" value="[A-Z].*?\." /> <button id="runBtn">Highlight</button>
</form>
<div id="test-cases" class="section">
<div>foo bar baz</div>
<p>
<b>HTML</b> is a language used to make <b>websites.</b>
It was developed by <i>CERN</i> employees in the early 90s.
<p>
<p>
This program is not stable yet. Do not use this in production yet.
</p>
<div>foo bar baz</div>
</div>
Ok, that was the lazy approach which, unfortunately doesn't work for some cases. It works well if you only highlight across inline elements, but breaks when there are block elements along the way because of the following property of the extractContents function:
Partially selected nodes are cloned to include the parent tags necessary to make the document fragment valid.
That's bad. It'll just duplicate block-level nodes. Try the previous demo with the baz\s+HTML regex if you want to see how it breaks.
Second approach
This approach iterates over the matching nodes, creating <span> tags along the way.
The overall algorithm is straightforward as it just wraps each matching node in its own <span>. But this means we have to deal with partially matching text nodes, which requires some more effort.
If a text node matches partially, it's split with the splitText function:
After the split, the current node contains all the content up to the specified offset point, and a newly created node of the same type contains the remaining text. The newly created node is returned to the caller.
function highlight(element, regex) {
var document = element.ownerDocument;
var nodes = [],
text = "",
node,
nodeIterator = document.createNodeIterator(element, NodeFilter.SHOW_TEXT, null, false);
while (node = nodeIterator.nextNode()) {
nodes.push({
textNode: node,
start: text.length
});
text += node.nodeValue
}
if (!nodes.length)
return;
var match;
while (match = regex.exec(text)) {
var matchLength = match[0].length;
// Prevent empty matches causing infinite loops
if (!matchLength)
{
regex.lastIndex++;
continue;
}
for (var i = 0; i < nodes.length; ++i) {
node = nodes[i];
var nodeLength = node.textNode.nodeValue.length;
// Skip nodes before the match
if (node.start + nodeLength <= match.index)
continue;
// Break after the match
if (node.start >= match.index + matchLength)
break;
// Split the start node if required
if (node.start < match.index) {
nodes.splice(i + 1, 0, {
textNode: node.textNode.splitText(match.index - node.start),
start: match.index
});
continue;
}
// Split the end node if required
if (node.start + nodeLength > match.index + matchLength) {
nodes.splice(i + 1, 0, {
textNode: node.textNode.splitText(match.index + matchLength - node.start),
start: match.index + matchLength
});
}
// Highlight the current node
var spanNode = document.createElement("span");
spanNode.className = "highlight";
node.textNode.parentNode.replaceChild(spanNode, node.textNode);
spanNode.appendChild(node.textNode);
}
}
}
// Test code
var testDiv = document.getElementById("test-cases");
var originalHtml = testDiv.innerHTML;
function test() {
testDiv.innerHTML = originalHtml;
try {
var regex = new RegExp(document.getElementById("regex").value, "g");
highlight(testDiv, regex);
}
catch(e) {
testDiv.innerText = e;
}
}
document.getElementById("runBtn").onclick = test;
test();
.highlight {
background-color: yellow;
}
.section {
border: 1px solid gray;
padding: 10px;
margin: 10px;
}
<form class="section">
RegEx: <input id="regex" type="text" value="[A-Z].*?\." /> <button id="runBtn">Highlight</button>
</form>
<div id="test-cases" class="section">
<div>foo bar baz</div>
<p>
<b>HTML</b> is a language used to make <b>websites.</b>
It was developed by <i>CERN</i> employees in the early 90s.
<p>
<p>
This program is not stable yet. Do not use this in production yet.
</p>
<div>foo bar baz</div>
</div>
This should be good enough for most cases I hope. If you need to minimize the number of <span> tags it can be done by extending this function, but I wanted to keep it simple for now.
function parseText( element ){
var stack = [ element ];
var group = false;
var re = /(?!\s|$).*?(\.|$)/;
while ( stack.length > 0 ){
var node = stack.shift();
if ( node.nodeType === Node.TEXT_NODE )
{
if ( node.textContent.trim() != "" )
{
var match;
while( node && (match = re.exec( node.textContent )) )
{
var start = group ? 0 : match.index;
var length = match[0].length + match.index - start;
if ( start > 0 )
{
node = node.splitText( start );
}
var wrapper = document.createElement( 'span' );
var next = null;
if ( match[1].length > 0 ){
if ( node.textContent.length > length )
next = node.splitText( length );
group = false;
wrapper.className = "sentence sentence-end";
}
else
{
wrapper.className = "sentence";
group = true;
}
var parent = node.parentNode;
var sibling = node.nextSibling;
wrapper.appendChild( node );
if ( sibling )
parent.insertBefore( wrapper, sibling );
else
parent.appendChild( wrapper );
node = next;
}
}
}
else if ( node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE )
{
stack.unshift.apply( stack, node.childNodes );
}
}
}
parseText( document.body );
.sentence {
text-decoration: underline wavy red;
}
.sentence-end {
border-right: 1px solid red;
}
<p>This is a sentence. This is another sentence.</p>
<p>This sentence has <strong>emphasis</strong> inside it.</p>
<p><span>This sentence spans</span><span> two elements.</span></p>
I would use "flat DOM" representation for such task.
In flat DOM this paragraph
<p>abc <a href="beta.html">def. ghij.</p>
will be represented by two vectors:
chars: "abc def. ghij.",
props: ....aaaaaaaaaa,
You will use normal regexp on chars to mark span areas on props vector:
chars: "abc def. ghij."
props: ssssaaaaaaaaaa
ssss sssss
I am using schematic representation here, it's real structure is an array of arrays:
props: [
[s],
[s],
[s],
[s],
[a,s],
[a,s],
...
]
conversion tree-DOM <-> flat-DOM can use simple state automata.
At the end you will convert flat DOM to tree DOM that will look like:
<p><s>abc </s><a href="beta.html"><s>def.</s> <s>ghij.</s></p>
Just in case: I am using this approach in my HTML WYSIWYG editors.
As everyone has already said, this is more of an academic question since this shouldn't really be the way you do it. That being said, it seemed like fun so here's one approach.
EDIT: I think I got the gist of it now.
function myReplace(str) {
myRegexp = /((^<[^>*]>)+|([^<>\.]*|(<[^\/>]*>[^<>\.]+<\/[^>]*>)+)*[^<>\.]*\.\s*|<[^>]*>|[^\.<>]+\.*\s*)/g;
arr = str.match(myRegexp);
var out = "";
for (i in arr) {
var node = arr[i];
if (node.indexOf("<")===0) out += node;
else out += "<span>"+node+"</span>"; // Here is where you would run whichever
// regex you want to match by
}
document.write(out.replace(/</g, "<").replace(/>/g, ">")+"<br>");
console.log(out);
}
myReplace('<p>This program is not stable yet. Do not use this in production yet.</p>');
myReplace('<p>This is a <b>sentence. </b></p>');
myReplace('<p>This is a <b>another</b> and <i>more complex</i> even <b>super complex</b> sentence.</p>');
myReplace('<p>This is a <b>a sentence</b>. Followed <i>by</i> another one.</p>');
myReplace('<p>This is a <b>an even</b> more <i>complex sentence. </i></p>');
/* Will output:
<p><span>This program is </span><span>not stable yet. </span><span>Do not use this in production yet.</span></p>
<p><span>This is a </span><b><span>sentence. </span></b></p>
<p><span>This is a <b>another</b> and <i>more complex</i> even <b>super complex</b> sentence.</span></p>
<p><span>This is a <b>a sentence</b>. </span><span>Followed <i>by</i> another one.</span></p>
<p><span>This is a </span><b><span>an even</span></b><span> more </span><i><span>complex sentence. </span></i></p>
*/
I have spent a long time implementing all of approaches given in this thread.
Node iterator
Html parsing
Flat Dom
For any of this approaches you have to come up with technique to split entire html into sentences and wrap into span (some might want words in span). As soon as we do this we will run into performance issues (I should say beginner like me will run into performance issues).
Performance Bottleneck
I couldn't scale any of this approach to 70k - 200k words and still do it in milli seconds. Wrapping time keeps increasing as words in pages keep increasing.
With complex html pages with combinations of text-node and different elements we soon run into trouble and with this technical debt keeps increasing.
Best approach : Mark.js (according to me)
Note: if you do this right you can process any number of words in millis.
Just use Ranges I want to recommend Mark.js and following example,
var instance = new Mark(document.body);
instance.markRanges([{
start: 15,
length: 5
}, {
start: 25:
length: 8
}]); /
With this we can treat entire body.textContent as string and just keep highlighting substring.
No DOM structure is modified here. And you can easily fix complex use cases and technical debt doesn't increase with more if and else.
Additionally once text is highlighted with html5 mark tag you can post process these tags to find out bounding rectangles.
Also look into Splitting.js if you just want split html documents into words/chars/lines and many more... But one draw back for this approach is that Splitting.js collapses additional spaces in the document so we loose little bit of info.
Thanks.
I am using two text areas. Project is about online typing test. I used two text area. First textarea contains the matter to be typed in second textarea. For calculating the the net typing speed I need a javascript diff algorithm.
Javascript Diff Algorithm algo fits my all requirements..which uses this
jsdiff.js
javascript file for differencing of two strings. and
JS Diff Demo
is a demo which uses the same javascript file...You should have look of this demo. But I how can I know count correct words typed? Trouble is that the javascript file provided is not using any comments nor gives any documentation.
I'm not sure if you need much more explanation than the comment I placed above. I like the diff-highlighting your link shows, but if all you're after is counting the diffs, why does something like this not work? http://jsfiddle.net/vSySu/
var arr1 = $('#text1').val().split(' ');
var arr2 = $('#text2').val().split(' '); // split on whatever string or regex you want.
var diffs = 0;
for (var i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
diffs++;
}
}
alert(diffs);
You could use a combination of a lenvenshtein algorithm to find the accuracy, and some basic string manipulation to count the words that are different. This can be improved but you get the idea:
function wordAccuracy(str1, str2) {
var len = str1.length,
distance = levenshtein(str1, str2),
words1 = str1.split(' '),
words2 = str2.split(' ');
return {
accuracy: 100 - (0|(distance * 100) / len) +'%',
fail: words1.filter(function(word, idx){
return word != words2[idx];
}).length
}
}
// Example:
var str1 = 'Lorem ipsum dolor sit amet consectetur adipiscing elit';
var str2 = 'Lorme ipsmu dolor sit maet cnsectetur adipiscing elot';
console.log(wordAccuracy(str1, str2));
//^ {
// accuracy: '86%'
// fail: 5
// }
I am currently learning Javascript, and I'd like to create my own Lorem Ipsum generator.
Basically, I would have a list of the paragraphs (in javascript, or in the HTML document?).
When the user presses the Generate button, it would then output 3 random paragraphs from the list.
I've looked around on here, but can't really find anything that helps.
Thanks
You could simply have a Javascript Array and pick a random index and inject that paragraph into the DOM element. I've also updated the code to not repeat the previous random integer per your comment below.
Example (code untested)
//global to store previous random int
_oldInt = null;
var paragraphArray = ["Lorem ipsum delor...", "The great white...", "Chitty-chitty-bang-bang..."];
//update element content (e.g. `<div>` with paragraph)
document.getElementById("MyID").innerHTML = pickRandom(paragraphArray);
var pickRandom = function(paragraphArray){
//random index of paragraphArray
var randomInt = Math.floor(Math.random()*paragraphArray.length);
//ensure random integer isn't the same as last
if(randomInt == _oldInt)
pickRandom(paragraphArray);
else{
_oldInt = randomInt;
return paragraphArray[randomInt];
}
}
You can use Math.random to generate a random index from your list:
var paragraphs = [...]; # This is your list of paragraphs
function get_random_paragraph() {
var index = Math.floor(paragraphs.length * Math.random());
return paragraphs[index];
};
The expression Math.floor(MAX_VALUE * Math.random()) generates a random integer x, where 0 <= x < MAX_VALUE.
You need some paragraphs (here, a JavaScript array), a result box (here, a <div>) and a button (here, a... <button>).
When you click on the button, you want to add a paragraphs into the result.
var paragraphs = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet'],
nbParagraphs = paragraphs.length
paragraph = null,
result = document.getElementById('result'),
button = document.getElementsByTagName('button')[0];
button.addEventListener('click', function() {
/*
* Math.random() return a number between 0 and 1
* parseInt() return an integer (the 10 is here to say that we are in decimal)
* parseInt(Math.random() * nbParagraphs, 10) return a number between 0 and the number of paragraphs, so we can use it to select a paragraph in the paragraphs array
*/
paragraph = paragraphs[parseInt(Math.floor(Math.random() * nbParagraphs, 10))]
result.innerHTML += '<p>' + paragraph + '</p>'
})
Here is a demo.
What I would like is to count the number of lines in a textarea, e.g:
line 1
line 2
line 3
line 4
should count up to 4 lines. Basically pressing enter once would transfer you to the next line
The following code isn't working:
var text = $("#myTextArea").val();
var lines = text.split("\r");
var count = lines.length;
console.log(count);
It always gives '1' no matter how many lines.
The problem with using "\n" or "\r" is it only counts the number of returns, if you have a line that is long it could wrap and then it wouldn't be counted as a new line. This is an alternative way to get the number of lines - so it may not be the best way.
Edit (thanks alex):
Script
$(document).ready(function(){
var lht = parseInt($('textarea').css('lineHeight'),10);
var lines = $('textarea').attr('scrollHeight') / lht;
console.log(lines);
})
Update: There is a much more thorough answer here: https://stackoverflow.com/a/1761203/145346
If you are just wanting to test hard line returns, this will work cross platform:
var text = $("#myTextArea").val();
var lines = text.split(/\r|\r\n|\n/);
var count = lines.length;
console.log(count); // Outputs 4
I have implemented the lines and lineCount methods as String prototypes:
String.prototype.lines = function() { return this.split(/\r*\n/); }
String.prototype.lineCount = function() { return this.lines().length; }
Apparently the split method will not count a carriage return and/or newline character at the end of the string (or the innerText property of a textarea) in IE9, but it will count it in Chrome 22, yielding different results.
So far I have accomodated for this by subtracting 1 from the line count when the browser is other than Internet Explorer:
String.prototype.lineCount = function() { return this.lines().length - navigator.userAgent.indexOf("MSIE") != -1); }
Hopefully someone has a better RegExp or another workaround.
user \n instead of \r
var text = $("#myTextArea").val();
var lines = text.split("\n");
var count = lines.length;
console.log(count);
However this is working if you need use it because it respond to your problem
let text = document.getElementById("myTextarea").value;
let lines = text.split(/\r|\r\n|\n/);
let count = lines.length;
console.log(count);
What about splitting on "\n" instead?
It will also be a problem where one line wrapped to 2 lines in the textarea.
To do it accurately like this, you could use a fixed height font and measure pixels. This could be problematic though.
This function counts the number of lines which have text in a textarea:
function countLine(element) {
var text = $(element).val();
var lines = text.split("\n");
var count = 0;
for (var i = 0; i < lines.length-1; i++) {
if (lines[i].trim()!="" && lines[i].trim()!=null) {
count += 1;
}
}
return count;
}
Counting the newlines is not a reliable way for finding the number of lines, since long text could simply break and still only count as a single line.
What you want to do, is find out the scrollHeight of the textarea and divide it by the height of a single line.
This is answered in detail here:
https://stackoverflow.com/a/1761203/9863305
I've used the original answer of Mottie but some functions were changed in the JQuery API. Here is the working function for the current API v3.1.0:
var lht = parseInt($('#textarea').css('lineHeight'),10);
var lines = $('#textarea').prop('scrollHeight') / lht;
console.log(lines);
All tumbs up for Mottie's answer!
This will aim to consider lines with both hard and soft returns:
//determine what the fontsize will be
let fontsize = 12;
//get number of characters that can fit in a row
let charsperrow = textarea.clientWidth / fontsize;
//get any hard returns
let hardreturns = textarea.textContent.split(/\r|\r\n|\n/);
let rows = hardreturns.length;
//loop through returns and calculate soft returns
for(let i = 0,len = rows; i < len; i++){
let line = hardreturns[i];
let softreturns = Math.round(line.length / charsperrow);
//if softreturns is greater than 0, minus by 1 (hard return already counted)
softreturns = Math.round(softreturns > 0 ? (softreturns - 1) : 0);
rows += softreturns;
}
console.log(Math.round(rows));
The normal newline character is "\n". The convention on some systems is to also have "\r" beforehand, so on these systems "\r\n" is often found to mean a new line. In a browser, unless the user intentionally enters a "\r" by copying it from somewhere else, the newline will probably be expressed as just "\n". In either case splitting by "\n" will count the number of lines.
<html>
<head>
<script>
function countLines(theArea){
var theLines = theArea.value.replace((new RegExp(".{"+theArea.cols+"}","g")),"\n").split("\n");
if(theLines[theLines.length-1]=="") theLines.length--;
theArea.form.lineCount.value = theLines.length;
}
</script>
</head>
<body>
<form>
<textarea name="myText" onKeyUp="countLines(this)" cols="10" rows="10">
</textarea>
<br>
Lines:
<input type=text name="lineCount" size="2" value="0">
</form>
</body>
</html>
Your ans can be done in very simple way.
var text = $("#myTextArea").val();
// will remove the blank lines from the text-area
text = text.replace(/^\s*[\r\n]/gm, "");
//It will split when new lines enter
var lines = text.split(/\r|\r\n|\n/);
var count = lines.length; //now you can count thses lines.
console.log(count);
This code for exact lines filled in the textarea.
and will work for sure.
Instead of textarea you could use a div with the attribute contenteditable="true". On a div with this attribute you can write anything, just like in a textarea, but any new line (except the first) is automatically wrapped inside a div. You can use jQuery or JS to count the div's and add +1, which is the first line.
It's a no brainer, i would use this instead of textarea with every occasion. It has several advantages. It auto resizes, you can easily count blank lines, you can customize every div or add spans with colors or font sizes or anything, you can use any line-height and any font-size, you can add rich text features and more, it's better for SEO and more. Here is a working example with jQuery:
$("#Editor").on("keyup mouseup", function(){
blankSpace = $(this).find("br").length; //count blank lines
urlCounter = $(this).find("div").length + 1 - blankSpace;
$(".lineCounter").text("Number of links: "+ urlCounter);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Editor" contenteditable="true" style="color:aqua;width: 100%;height: 100%;background: blue;"></div>
<div class="lineCounter" style="position: absolute;bottom: 0;z-index: 999;left: 0;"></div>
Try calling this function every time you change its value.
textArea.addEventListener('input', function() {
setDynamicHeight();
});
function setDynamicHeight() {
textArea.style.height = 0; // set the height to 0 in case of it has to be shrinked
textArea.style.height = textArea.scrollHeight + 'px'; // set the dynamic height
}
Each line break is defined by '\n'. The goal is to count them. For this, we will have to iterate on this with a loop on each character. See the example below
let count = 0
const a = document.querySelector('textarea')
for (let i = 0; i < a.value.length; i++) {
if (a.value[i] == '\n') {
count++
}
}
console.log(count)
In this live demonstration we can see a concrete case with 3 sentences :
const textareaLineCount = () => {
let count = 0
const a = document.querySelector('textarea')
for (let i = 0; i < a.value.length; i++) {
if (a.value[i] == '\n') {
count++
}
}
return count
}
const displayTotalTextareaLine = (total) => {
document.querySelector('p').innerText = total
}
document.querySelector('button').addEventListener('click', () => {
const total = textareaLineCount()
displayTotalTextareaLine(total)
})
<textarea>
Lorem ipsum dolor sit amet.
Lorem ipsum dolor sit amet.
Lorem ipsum dolor sit amet.
</textarea>
<button>count</button>
<p></p>
⚠️ It's possible that the last phase is not a line break, so I advise you to add 1 to the total result