I have two textareas written in HTML like this:
<textarea id="checked-words"></textarea>
<br />
<textarea id="words" onkeyup="displayColoredText()"></textarea>
<div id="text-to-insert"></div>
(into the div element, I will insert text, using JavaScript)
My task is to write into the div section the text from the second textarea and make red the occurrences of strings from the first textarea.
Example
If the first textarea contains the following words: aaa, aab and the second contains aaab, all of the characters have to be red. If the second one contains abaa, none of the characters will be red in the div section.
Here is my JavaScript function which displays and colorize the text:
function displayColoredText() {
//Displays the colored text below the second textarea
//Find the two textareas
var firstTextArea = document.getElementById('checked-words');
var secondTextArea = document.getElementById('words');
//Split by spaces
var checkedWords = firstTextArea.value.split(" ");
var text = secondTextArea.value.split(" ");
var textToInsert = secondTextArea.value;
for(i in checkedWords) {
console.log(checkedWords.length);
textToInsert = textToInsert.replace(new RegExp(checkedWords[i], 'g'), '<span class="insertRed">' + checkedWords[i] + '</span>');
}
document.getElementById('text-to-insert').innerHTML = textToInsert;
}
My problem is, that an already replaced text won't be considered, for example, if the first textarea contains aaa and aab and if the second one contains aaab, only the first three characters will be red, instead of the whole string. How can I resolve this?
EDIT: Screenshot of the problem
Your original input from the second text area is pure text, not HTML, so this is the "state" of the data you want to do this in.
This would be my way of implementing it as mentioned in comments, recording which positions have a match first, and then simply looping over all characters in the end to wrap them in a span each:
function displayColoredText() {
//Displays the colored text below the second textarea
//Find the two textareas
var firstTextArea = document.getElementById('checked-words');
var secondTextArea = document.getElementById('words');
//Split by spaces
var checkedWords = firstTextArea.value.split(" ");
var text = secondTextArea.value;
// empty array with keys 0 to length-1 set to undefined
var markedMatches = new Array(secondTextArea.value.length);
for (var i = 0, l = checkedWords.length; i < l; ++i) {
var checkedWord = checkedWords[i],
start = 0,
matchPos;
// check for match from current starting position
while ((matchPos = text.indexOf(checkedWord, start)) !== -1) {
// mark positions from current match start to that plus length of match
for (var k = matchPos, m = matchPos + checkedWord.length; k < m; ++k) {
markedMatches[k] = true;
}
// advance starting position to continue searching
start = matchPos + 1;
}
}
var textToInsert = '';
for (var i = 0, l = text.length; i < l; ++i) {
// wrap in span if markedMatches contains true at this position
textToInsert += (markedMatches[i] ? '<span class="match">' + text[i] + '</span>' : text[i]);
}
document.getElementById('text-to-insert').innerHTML = textToInsert;
}
https://jsfiddle.net/t9xjzkaw/
As I said, you could get more sophisticated in collecting the matches as intervals, or putting multiple adjoining matching characters into a single span element, instead of wrapping each one on its own ... but this does the basic job.
Your problem is how your strings get replaced. Your first string is 'aaa aab'. After replacing for 'aaa' in 'aaab', you get '<span class="insertRed">aaa</span>b'. Trying to find 'aab' in this string will come up with no results. You have to replace from your original string and somehow combine the two. I'm not sure how to do this, but I hope this sets you on the right track.
EDIT:
I think this will work:
Instead of replacing the text in the string, place the beginning coordinate in an array and the end coordinate in a second array. Keep doing this for every word found. Then at all of the beginning coordinates, insert the string '<span class="insertRed">'. At all of the end coordinates, insert the string '<span>'. Here is the JS:
function displayColoredText() {
//Displays the colored text below the second textarea
//arrays with coordinates
var beginnings = [];
var ends = [];
//Find the two textareas
var firstTextArea = document.getElementById('checked-words');
var secondTextArea = document.getElementById('words');
//Split by spaces
var checkedWords = firstTextArea.value.split(" ");
var text = secondTextArea.value.split(" ");
var textToInsert = firstTextArea.value;
for(i in checkedWords) {
console.log(checkedWords.length);
if (firstTextArea.value.indexOf(checkedWords[i]) != -1) {
beginnings.push(firstTextArea.value.indexOf(checkedWords[i]));
ends.push(firstTextArea.value.indexOf(checkedWords[i]) + checkedWords[i].length);
}
}
beginnings.sort(function(a, b){return b-a});
ends.sort(function(a, b){return b-a});
for (b in beginnings) {
textToInsert = textToInsert.slice(0, ends[b]) + "</span>" + textToInsert.slice(ends[b]);
textToInsert = textToInsert.slice(0, beginnings[b]) + '<span class="insertRed">' + textToInsert.slice(beginnings[b]);
}
document.getElementById('text-to-insert').innerHTML = textToInsert;
}
This code is untested, so tell me if something doesn't work and I will change it. Basically, what I am doing is instead of replacing occurrences, I find them first, place them in arrays, and insert the correct text at those coordinates. I hope this helps!
Related
I'm not sure this is possible but I wondered if there is a way of targeting the last word in a line of text within a paragraph. Note that I'm not looking for the last word in the paragraph but the last word in a line of said paragraph.
I haven't the faintest idea of how to start with this so haven't got an attempt formulated.
Thanks for any help you can offer!
Mark
I found a JS library, jsLineWrapDetector, which can retrieve the lines from the text wrapped by DOM element.
var p = $("p")[0];
var lines = lineWrapDetector.getLines(p);
lines.forEach(function(line) {
var lastword = line.split(" ").pop();
});
You can get the text of p tag, split them on occurrence of space to create the array of word. then target the last element in returned array. Like this:
var wordarray = $('p').text().split(" ");
return wordarray [wordarray.length - 1];
You can use this function :
function getLastWord(paragraph, line) {
return paragraph.split("\n")[line].split(" ").pop();
}
Working Example
The code below will find the last word of each line (of a p tag), and throw all of them in the console. words[i - 1], or breakWord is the last word of each line. Hopefully, this helps.
var breakWord
var p = $('p');
p.each(function () {
var current = $(this);
var text = current.text();
var words = text.split(' ');
current.text(words[0]);
var height = current.height();
for (var i = 1; i < words.length; i++) {
current.text(current.text() + ' ' + words[i]);
if (current.height() > height) {
height = current.height();
breakWord = words[i - 1];
console.log(breakWord);
}
}
console.log(current);
});
This code will split the whole paragraph up into single words, put them in an array (words), and then check for line breaks. When a line break is found, it returns the last word of the line.
Given this HTML as a string "html", how can I split it into an array where each header <h marks the start of an element?
Begin with this:
<h1>A</h1>
<h2>B</h2>
<p>Foobar</p>
<h3>C</h3>
Result:
["<h1>A</h1>", "<h2>B</h2><p>Foobar</p>", "<h3>C</h3>"]
What I've tried:
I wanted to use Array.split() with a regex, but the result splits each <h into its own element. I need to figure out how to capture from the start of one <h until the next <h. Then include the first one but exclude the second one.
var html = '<h1>A</h1><h2>B</h2><p>Foobar</p><h3>C</h3>';
var foo = html.split(/(<h)/);
Edit: Regex is not a requirement in anyway, it's just the only solution that I thought would work for generally splitting HTML strings in this way.
In your example you can use:
/
<h // Match literal <h
(.) // Match any character and save in a group
> // Match literal <
.*? // Match any character zero or more times, non greedy
<\/h // Match literal </h
\1 // Match what previous grouped in (.)
> // Match literal >
/g
var str = '<h1>A</h1><h2>B</h2><p>Foobar</p><h3>C</h3>'
str.match(/<h(.)>.*?<\/h\1>/g); // ["<h1>A</h1>", "<h2>B</h2>", "<h3>C</h3>"]
But please don't parse HTML with regexp, read RegEx match open tags except XHTML self-contained tags
From the comments to the question, this seems to be the task:
I'm taking dynamic markdown that I'm scraping from GitHub. Then I want to render it to HTML, but wrap every title element in a ReactJS <WayPoint> component.
The following is a completely library-agnostic, DOM-API based solution.
function waypointify(html) {
var div = document.createElement("div"), nodes;
// parse HTML and convert into an array (instead of NodeList)
div.innerHTML = html;
nodes = [].slice.call(div.childNodes);
// add <waypoint> elements and distribute nodes by headings
div.innerHTML = "";
nodes.forEach(function (node) {
if (!div.lastChild || /^h[1-6]$/i.test(node.nodeName)) {
div.appendChild( document.createElement("waypoint") );
}
div.lastChild.appendChild(node);
});
return div.innerHTML;
}
Doing the same in a modern library with less lines of code is absolutely possible, see it as a challenge.
This is what it produces with your sample input:
<waypoint><h1>A</h1></waypoint>
<waypoint><h2>B</h2><p>Foobar</p></waypoint>
<waypoint><h3>C</h3></waypoint>
I'm sure someone could reduce the for loop to put the angle brackets back in but this is how I'd do it.
var html = '<h1>A</h1><h2>B</h2><p>Foobar</p><h3>C</h3>';
//split on ><
var arr = html.split(/></g);
//split removes the >< so we need to determine where to put them back in.
for(var i = 0; i < arr.length; i++){
if(arr[i].substring(0, 1) != '<'){
arr[i] = '<' + arr[i];
}
if(arr[i].slice(-1) != '>'){
arr[i] = arr[i] + '>';
}
}
Additionally, we could actually remove the first and last bracket, do the split and then replace the angle brackets to the whole thing.
var html = '<h1>A</h1><h2>B</h2><p>Foobar</p><h3>C</h3>';
//remove first and last characters
html = html.substring(1, html.length-1);
//do the split on ><
var arr = html.split(/></g);
//add the brackets back in
for(var i = 0; i < arr.length; i++){
arr[i] = '<' + arr[i] + '>';
}
Oh, of course this will fail with elements that have no content.
Hi I used this function to convert html String Dom in array
static getArrayTagsHtmlString(str){
let htmlSplit = str.split(">")
let arrayElements = []
let nodeElement =""
htmlSplit.forEach((element)=>{
if (element.includes("<")) {
nodeElement = element+">"
}else{
nodeElement = element
}
arrayElements.push(nodeElement)
})
return arrayElements
}
Happy code
In JavaScript, by given a html string with tags like:
This string has different fonts.
<b>This</b>
<i>string</i>
<span style="background-color: rgb(212, 239, 181);">has</span>
<b>different</b>
<i>fonts</i>.
When user searches for a search term with multiple words like "different fonts".
How can I add highlighting to make the html string look like:
<b>This</b>
<i>string</i>
<span style="background-color: rgb(212, 239, 181);">has</span>
<span class="highlight">
<b>different</b>
<i>fonts</i>
</span>.
Please note that the search term is treated as a single string as if the words are in a quote, so I cannot search and highlight each word individually.
Utilize innerHTML + str.replace in Javascript
Start by placing a div around your content.
<div id='content'>
Set the content to a variable in Javascript.
var xyz = document.getElementById("content").innerHTML;
Explode the users search into an array and use Javascripts str.replace to filter through the content variable.
var needle = document.getElementById("needle").innerHTML;
var splitArray = str.split(" ");
for (var i = 0; i < splitArray.length; i++) {
/* code below mentioned later */
xyz = str.replace("<span class='highlight'>" + splitArray[i] + "</span>", splitArray[i]);
}
set the replace text to:
"<span class='highlight'>" + /* needle (see above) */ + "</span>"
To keep them consecutive, go through the xyz string and replace the span tags with 3 character replacements:
var replaceS = xyz.replace("</span>", "~/S");
var replaceS = replaceS.replace("<span class='bold'>", "~/B");
Then loop through the string using indexOf. If there is a split array that has an index of the previous split array.length + 7 then you know it is consecutive. The 7 comes from the space between the words + the 3 character replacement you made for the start and end spans: 7 = 3 + 1 + 3
var countR = 0;
// CREATE A LOOP HERE
var indxStr = replaceS.indexOf(splitArray[0],countR);
if (replaceS.indexOf(splitArray[1] == (indxStr.length + 7) {
// good so far!
} else {
// get the previous index and add one to it (so that we do not repeat)
countR = replaceS.indexOf(indxStr) + 1;
}
// END YOUR LOOP HERE
Where I put "CREATE YOUR LOOP HERE" you want loop so that you can find multiple occurrences of your string (indexOf only gets the first, that is why we used the countR variable to avoid the previous search). The "good so far" comment is to continue on this same style of loop, this loop will only handle 2 words "different fonts" should there be 3 like "has different fonts" then you will need a loop that counts per word ->
for (var q = 0; q < splitArray.length; q++) {
if (q > 1) {
if (replaceS.indexOf(splitArray[q] == (splitArray[q - 1].length + 7) {
}
}
} else {
// use original code above with splitArray[1] and indxStr
}
// etc~
I have an H3 element that I'm currently using to initialize a game
<h3 id="startGame">Start</h3>
The following is the JavaScript that I've written
In the function formatText() I take what the user has inputted and make the entire string lower case. I then capitalize the first letter of the word so that it matches how the strings are written in the array. In the case where there is actually two words, I then grab the first letter of the second word and capitalize that to match the way the two word strings are written in the array.
In the end when a user inputs what I've asked them to input, it shouldn't matter how they wrote it (in regards to capitalization). All that should matter is that they spelled it right.
However, my problem is that it works for the one word strings but does not capitalize the second word as I intended. Meaning I can enter the one word strings in any manner I wish (spelled correctly of course) and it will resolve to correct. I can even input the two word strings and mess with the capitalization of the first word and it will resolve to correct. However when I do not capitalize the second word it always resolves to incorrect.
The code that I've written to resolve this issue doesn't seem to work and I don't know why.
var nutrients = [
"Vitamin B6",
"Manganese",
"Vitamin C",
"Fiber",
"Potassium",
"Biotin",
"Copper"
];
function memoNutri() {
var pleaseCopy;
var spaceMarker = " ";
var capitalizeSecondWord;
var firstWord;
var secondWord;
var twoWords;
function ask() {
pleaseCopy = prompt("Enter the following into the text field: " + nutrients[i] + ".");
}
function formatText() {
pleaseCopy.toLowerCase();
pleaseCopy = pleaseCopy[0].toUpperCase() + pleaseCopy.substring(1, pleaseCopy.length);
capitalizeSecondWord = pleaseCopy.substring(spaceMarker + 1, spaceMarker + 2).toUpperCase();
firstWord = pleaseCopy.substring(0, spaceMarker);
secondWord = capitalizeSecondWord + pleaseCopy.substring(spaceMarker + 2, pleaseCopy.length);
twoWords = firstWord + spaceMarker + secondWord;
}
for (i = 0; i < nutrients.length; i++) {
ask();
formatText();
if (pleaseCopy === nutrients[i] || twoWords === nutrients[i]) {
alert("You are correct! " + nutrients[i]);
} else {
alert("That is incorrect");
}
}
}
var startGame = document.getElementById('startGame');
startGame.onclick = memoNutri;
Try using this:
var textArr = pleaseCopy.split(" ");
for (var i = 0; i < textArr.length; i++) {
textArr[i] = textArr[i][0].toUpperCase() + textArr[i].substring(1).toLowerCase();
}
var twoWords = textArr.join(" ");
Split the content into an array. Format the text at each index, and the re-join the text.
I have a JavaScript array which contain the staff's Chinese and English names.
Example:
XXX MA Yo-Yo
Where XXX represents the Chinese name: 馬友友.
I want to split this into two parts by using the 1st space " " as an indicator.
for (i = 0; i < /* ... */)
{
w_temp = arr[i];
w_name = w_temp[1].split(' ', 1);
/* ... */
}
w_name[0] successfully returns 馬友友. But w_name[1] returns undefined, which cannot return MA Yo-Yo.
How can I split it into two parts?
Replace your existing w_name = ... line with something like this:
w_name = w_temp[1].split(' ');
w_name = [w_name[0], w_name.slice(1).join(' ')];
The ,1 you had earlier meant to stop parsing as soon as it came to the first space. This is not what you want; you want the part before the first space as one item and the part after as another. The new code parses all of the elements. After that, it sets it to an array consisting of:
The already-existing first element, and
The parts after the first element re-joined.
You have
split(' ', 1)
The 1 means to return at most one part. You should just ommit that or change it to two if thats what you need.
Because you invoke split method with limit = 1, then w_name has only 1 item.
In addition, yours input string has 2 whitespace, therefore, if you use split without limit parameter, w_name will contains 3 items ('XXX', 'CHAN', and 'Tai-Man').
I think you should use this:
var idx = str.indexOf(' ');
if (idx != -1) {
var cn_name = str.substring(0, idx);
var en_name = str.substring(idx + 1);
}
Try this
for (var i = 0; i < arr.length; i++) {
var w_temp = arr[i];
// assuming w_temp contains an array where the second item
// is the full name string
var parts = w_temp[1].match(/^(.+?) (.+)$/);
var cn_name = parts[1];
var en_name = parts[2];
}