Surrounding each word from user selected text with a DOM element - javascript

From a DOM fragment coming from the user selected text window.getSelection():
<h1>Hello world!</h1>
<p>How <b>do y<i>o<i>u</b> do?</p>
I'd like to surround a <span class="foo"></span> around each word, ie:
<h1><span class="foo">Hello</span> <span class="foo">world!</span></h1>
<p><span class="foo">How</span> <b><span class="foo">do</span> <span class="foo">y<i>o<i>u</span></b> <span class="foo">do?</span></p>
How can I do this in javascript?
For now here is what I have:
// Get highlighted text
var selection = window.getSelection();
// Iterate over ranges
for (var i = 0, l = selection.rangeCount; i < l; ++i) {(function () {
var range = selection.getRangeAt(i);
var fragment = range.cloneContents();
// HELP HERE: Surround each word in the fragment...
}())}

Related

How to convert all the elements of a for loop to a string

I have multiple letters, each written in their own span under an h1 tag, written in the HTML file. I then want to loop over these letters, and combine all the letters from the span elements into a single string that looks like this, "Hover over me!" (with the spaces). I have completed the for loop and extracted the inner HTML for each letter, but am having a hard time converting this to a single string, here is my HTML and JS code.
let text = document.querySelectorAll(".letter");
for (let i = 0; i < text.length; i++) {
let array = [];
let letters = text[i].innerHTML;
console.log(letters);
}
<h1>
<span class="letter">H</span>
<span class="letter">o</span>
<span class="letter">V</span>
<span class="letter">E</span>
<span class="letter">R</span>
<span> </span>
<span class="letter">O</span>
<span class="letter">V</span>
<span class="letter">E</span>
<span class="letter">R</span>
<span> </span>
<span class="letter">M</span>
<span class="letter">E</span>
<span class="letter">!</span>
</h1>>
Get all the span elements, iterate over them taking their text content, and shove that into an array. If the letter is at first index of the str make it uppercase, otherwise lowercase. Then join up the string, and either log it to the console, or add it as the text content of another element as I've done here.
(I removed all the ids because an id needs to be unique, and they were mostly redundant here.)
const output = document.querySelector('.output');
const spans = document.querySelectorAll('span');
// The array is _outside_ of the loop
const arr = [];
for (let i = 0; i < spans.length; i++) {
// Get a letter at the current index
const letter = spans[i].textContent;
// If it's zero uppercase the letter
// otherwise lowercase it, and push it to
// the array
if (i === 0) {
arr.push(letter.toUpperCase());
} else {
arr.push(letter.toLowerCase());
}
}
// `join` the array into a string, and
// either log it or add it as the text content
// of another element
output.textContent = arr.join('');
console.log(arr.join(''));
<h1>
<span>H</span>
<span>o</span>
<span>V</span>
<span>E</span>
<span>R</span>
<span> </span>
<span>O</span>
<span>V</span>
<span>E</span>
<span>R</span>
<span> </span>
<span>M</span>
<span>E</span>
<span>!</span>
</h1>
<div class="output"></div>
Assuming you have correctly populated the text array (and noting you could probably combine the extraction and string build in a single loop), the direct answer is:
var letters;
for (let i = 0; i < text.length; i++) {
let letters += text[i].innerText;
}
console.log(letters);
Edited, based on comments & also probably we don't want to iteratively log the process of our loop, so moving the log out of the loop.
What about this solution:
// Select the characters by the tag...
const letters = document.getElementsByTagName('span');
// ... or by the class name
// const letters = document.getElementsByClassName('letter');
// Your final string
let str = '';
// Iterate over array of letters
for (let char of letters) {
// Write each single character into variable 'str'
str += char.textContent;
}
// Your result
console.log(str);
<!-- Don't apply same id to several elements, that's invalid HTML. Use classes instead -->
<h1>
<span class="letter">H</span>
<span class="letter">o</span>
<span class="letter">V</span>
<span class="letter">E</span>
<span class="letter">R</span>
<span> </span>
<span class="letter">O</span>
<span class="letter">V</span>
<span class="letter">E</span>
<span class="letter">R</span>
<span> </span>
<span class="letter">M</span>
<span class="letter">E</span>
<span class="letter">!</span>
</h1>

How can I get the matched element's parent div attribute by regular express using Javascript

Have a html string see below.
<div sentence="11">
<p>
how are you Tom, how are you Tom
</p>
</div>
<div sentence="12">
<p>
how are you Tom
</p>
</div>
When user select the name 'Tom', I will replace the string 'Tom' using a name tag with some html style. The result what I want see below.
<div sentence="11">
<p>
how are you <span class="ano-subject" data-oristr="Tom" data-offset="1" data-sentence="11"><NAME></span>, how are you <span class="ano-subject" data-oristr="Tom" data-offset="2" data-sentence="11"><NAME></span>
</p>
</div>
<div sentence="12">
<p>
how are you <span class="ano-subject" data-oristr="Tom" data-offset="1" data-sentence="12"><NAME></span>
</p>
</div>
I will using the code below to do the replace.
var content = HTML CODE ABOVE;
var selectText = 'Tom';
var regex = new RegExp('(?=\\s|^|\\b)(?:' + selectText + ')(?=\\s|$|\\b)', "g");
var pre_sentence = 0;
var offset = 0;
content = content.replace(regex, function(match, position) {
var curr_sentence = ???; // how can I get this element's parent div attribute 'sentence' ?
if(pre_sentence != cur_sentence){
// this is new sentence.
offset = 1;
pre_sentence = curr_sentence;
} else {
offset++;
}
var replace = '<span class="ano-subject" data-oristr="Tom" data-offset="'+offset+'" data-sentence="'+curr_sentence+'"><NAME></span>';
return replace;
});

Select sibling dom element based on the text content of one of sibling

I have following html structure -
<p>
<span class="font-weight-medium">
Phone Number:
</span>
<br>
<span>(123) 456-7869</span>
</p>
Now I want to select all such span which have phone number using pure javascript or css selector.
I am using x-ray.
I had tried this but this doesn't seems to work.
x(html,'span[contains(text(),"Phone Number")]')
(function(err, obj) {
console.log(obj);
});
If you are fine using jQuery, then, here is the answer:
$("span:contains(Phone Number:)~span")
or this
$('span:contains(Phone Number:)').siblings('span')
You can get all span elements and loop to find next span after those containing Phone Number:
span_tags = document.getElementsByTagName('span');
for (var i=0, max=span_tags.length; i < max; i++) {
el = span_tags[i];
str = el.innerHTML;
regex = / *Phone Number: */;
if ( str.match(regex)) {
console.log(span_tags[i+1].innerHTML);
}
}
<p>
<span class="font-weight-medium">
Phone Number:
</span>
<span>(123) 456-7869</span>
</p>
I'm not sure there is a css selector based solution (:contain is a jquery pseudo selector). But here is another approach using indexOf:
span_tags = document.getElementsByTagName('span');
for (var i=0, max=span_tags.length; i < max; i++) {
if (span_tags[i].innerHTML.indexOf("Phone Number:") != -1) {
console.log(span_tags[i+1].innerHTML);
}
}
<p>
<span class="font-weight-medium">
Phone Number:
</span>
<span>(123) 456-7869</span>
</p>

window.getSelection() on a semantic element like <strong>

Let's say I have this html
<strong>Link</strong>
and I want to replace this with something else programatically. I select this with the mouse and call
var sel = window.getSelection()
The content of sel is however a text, and its parentNode is the link node a (and its parentNode is the <strong> element I was looking for).
Can I get semantic elements like e.g. strong, b, em in a selection?
Use case: I want to select some text in a wysiwyg editor (html) and replace it with a link.
You can use the jQuery selector feature to search in the string for particular tag:
$('#btn').on('click', function() {
var sel = getSelectionHtml();
alert(sel.toString())
var anchor = $(sel).find('a');
var id = anchor.attr('id');
// here DOM manipulation starts
$('#' + id).html('Click here');
});
function getSelectionHtml() {
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
}
else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>This is sample text</span>
<br/>
<br/>
<strong>Link</strong>
<span>This is another text</span>
<br/>
<br/>
<span>Select two above lines and click the button below</span>
<br/>
<br/>
<input type="button" id="btn" value="Click to see selected HTML" />
The function used to obtain HTML selection was reused from this page
Are you sure that there wouldn't be other <strong> tags on the page? This isn't unique at all, so you would be running a risk of selecting/replacing the wrong element. You could do something where you're finding the first or closest, but this may still be risky (could even grab this in a text-based ad that you're running).
That said, if you feel comfortable with the approach, you could try something like this instead:
document.getElementsByTagName("strong")[0].innerHTML = "<a href='url'>Some words</a>";
That would grab the first <strong> tag on the page and inject the link for you.

Why nodeValue is not getting me the text between the <span>

I have the following HTML:
<html>
<body>
<div>
<span> $12.95 </span>
</div>
</body>
</html>
And the following Javascript:
var all = document.body.getElementsByTagName("*");
for (var i=0, max=all.length; i < max; i++) {
console.log(all[i].nodeValue);
}
I see null in the console when it gets to the element. I am wondering how may I be able to get just the text of all the elements in a page? I know that if I use innerHTML I would get the text, but then I would get the text repeated somehow. So, for the <div> I would get <span> $12.95 </span> and then for the <span> I would get $12.95
If you want to use nodeValue to get the contents then you have to traverse down to the text node that is contained within the span.
http://jsfiddle.net/xLJMb/
var all = document.body.getElementsByTagName("*");
for (var i=0, max=all.length; i < max; i++) {
console.log(all[i].nodeValue);
for(var j = 0, max2 = all[i].childNodes.length; j < max2; j++) {
console.log(all[i].childNodes[j].nodeValue);
}
}
Text Nodes are not elements, so they are not returned directly by getElementsByTagName().
Why do not use from this html:
<div>
<span id="span">$12.95 </span>
</div>
and this Script:
console.log($('#span').html());
As addendum to the answer above, in modern browser, if you want to iterate only text node, you could use the TreeWalker API:
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
// Using ES6 arrow function, this is removing all "empty" text nodes
// equivalent to:
// function (node) { return !!node.nodeValue.trim() }
node => !!node.nodeValue.trim()
);
while(treeWalker.nextNode())
console.log(treeWalker.currentNode.nodeValue);

Categories

Resources