Welcome to WebmasterWorld Guest from 3.92.92.168

Forum Moderators: open

Message Too Old, No Replies

Getting the surrounding tags of selection

     
7:45 am on Jul 23, 2018 (gmt 0)

Senior Member

WebmasterWorld Senior Member 5+ Year Member Top Contributors Of The Month

joined:Mar 15, 2013
posts: 1205
votes: 120


I've been working on this for 3 days, so now I guess it's time to turn to the pros again :-(

Let's say I have this:

<div id="foo">
<font face="Arial"><font size="7"><b>This is <i>a</i> test</b></font></font>
</div>


When the user selects the text, I'm trying to get the whole selection, including the surrounding HTML code. But I can't seem to figure out how to get it! I can get the child <i></i>, but that's all.

This is as close as I've gotten:


var text;

$('#foo').on('mouseup', function() {
if (window.getSelection) {
var div = document.createElement('div');
div.appendChild(window.getSelection().getRangeAt(0).cloneContents());

text = div.innerHTML;
}

alert(text);


How do I get the surrounding tags of the selection?

I felt like I was making some progress here, but then I got stuck:

// use this to expand the selection, forcing it to pick up the next word boundary
function expand(range) {
if (range.collapsed)
return;

while (range.toString()[0].match(/\w/))
range.setStart(range.startContainer, range.startOffset - 1);

while (range.toString()[range.toString().length - 1].match(/\w/))
range.setEnd(range.endContainer, range.endOffset + 1);
}

$('#foo').on('mouseup', function() {
var sel = window.getSelection();
var cont = document.createElement("div");
var selectionRange, selection;

if (sel.rangeCount) {
// I know it's probably overkill to put it in a loop, but since I'm
// still in production, why not...
for (var i = 0; i < sel.rangeCount; i++) {
selectionRange = sel.getRangeAt(i);

var start = selectionRange.startOffset;

// this works, and expands the selection like expected
expand(selectionRange);

// if I double-click then this alerts, but if I click and drag it doesn't
alert('yeah?');

cont.appendChild(selectionRange.cloneContents());
}

selection = cont.innerHTML;
}

// either way, selection is always empty
if (selection)
alert(selection);

else alert('no selection');
});


If it matters, the end goal here is to surround the selected text with something like <span style="font-size: 120%"></span>. But the problem I'm having is when they already have a font size selected then it keeps compounding, like:

<span style="font-size: 220%"><span style="font-size: 100%"><span style="font-size: 160%">This is <i>a</i> test</span></span></span>


The only way I can think of to get around this is to capture the surrounding tags and remove style="font-size: [0-9]+%", then surround it again.

Of course, I know that won't fix things like this:

<span style="font-size: 100%">Hey you, <span style="font-size: 160%">This is <i>a</i> test</span></span>


but it's better than nothing.
1:41 pm on July 23, 2018 (gmt 0)

Senior Member from GB 

joined:Oct 2, 2003
posts: 1019
votes: 39


Hi there csdude55,
I am not going to work with "font elements" and neither should you.

Try instead this example...


<!DOCTYPE HTML>
<html lang="en">
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">

<title>untitled document</title>

<link rel="stylesheet" href="screen.css" media="screen">

<style media="screen">

body {
background-color: #f9f9f9;
font: 100% / 162% verdana, arial, helvetica, sans-serif;
}

#foo {
padding: 0.5em 0;
font-family: arial;
text-align: center;
}

.two-em {
font-size:2em;
}

.three-em {
font-size:3em;
}
</style>

</head>
<body>

<div id="foo" class="three-em">
<b>This is <i>a</i> test</b>
</div>

<script>
( function( d ) {
'use strict';
var sp, foo = d.getElementById( 'foo' ),
b = d.getElementsByTagName( 'b' )[0],
parent = b.parentNode;

foo.addEventListener( 'mouseup',
function() {
foo.classList.remove( 'three-em' );
foo.classList.add( 'two-em' );
sp = document.createElement( 'span' );
sp.appendChild(document.createTextNode( 'Hey you, '));
parent.insertBefore( sp, b )
}, false );

}( document ) );
</script>

</body>
</html>


birdbrain
1:56 pm on July 23, 2018 (gmt 0)

Senior Member from GB 

joined:Oct 2, 2003
posts: 1019
votes: 39


Hi there csdude55,
I forgot to mention that the "mouseup" event handler could be replaced
by "click", if preferred, as the code is not now dependent upon selection. ;)


birdbrain
2:06 am on July 24, 2018 (gmt 0)

Senior Member

WebmasterWorld Senior Member 5+ Year Member Top Contributors Of The Month

joined:Mar 15, 2013
posts: 1205
votes: 120


Unfortunately, I kinda have to work within the selection. This is in a contenteditable, and I'm letting the user change the font family and font size. They can create headlines and things on the fly, so I can't just change it on the entire element; I need to just change it on the text they have selected.

If I'm reading your code correctly, it would remove all instances of "three-em", and add "two-em" to all of them. But I need it to look more like:

# this is the code for the contenteditable element
<div contenteditable="true" style="font-size: 2em">

# this is what's entered by the user
<span style="font-size: 220%">Getting the surrounding tags of selection</span><br><br><span style="font-size: 120%">I've been working on this for 3 days, so now I guess it's time to turn to the pros again :-(</span>
</div>


I do use classes, though; I just showed it here inline so it would be easier for you guys to read. I'm using percentages, though, so that it will make sense to the reader; they have the option of increasing the default font size to whatever they like, so I need a headline to look bigger than the paragraph text regardless of what the reader's default font is.

I've been playing with the last code I posted, and I found where it's breaking, but I'm not sure how to fix it. The problem is when it sets selectionRange.setEnd(selectionRange.endContainer, selectionRange.endOffset + 1), I get the following error:

Failed to execute 'setEnd' on 'Range': The offset 6 is larger than the node's length (5).

So I need a condition to make sure that selectionRange.endOffset + 1 doesn't exceed the number of nodes. But I can't seem to find a way to get that number!

while (
selectionRange.toString()[selectionRange.toString().length - 1].match(/\w/) &&

# sometimes this is NaN
selectionRange.endOffset &&
!isNaN(selectionRange.endOffset) &&

# how do I get the node length?
(selectionRange.endOffset + 1) <= [NODE LENGTH]
) {
selectionRange.setEnd(selectionRange.endContainer, selectionRange.endOffset + 1);
}
3:35 pm on July 24, 2018 (gmt 0)

Senior Member from GB 

joined:Oct 2, 2003
posts: 1019
votes: 39


Hi there csdude55,
if you are going to have "contenteditable",
then you will not be able to use "mouseup".

Here is a possible solution...



<!DOCTYPE HTML>
<html lang="en">
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">

<title>untitled document</title>

<link rel="stylesheet" href="screen.css" media="screen">

<style media="screen">
body {
background-color: #f9f9f9;
font: 100% / 162% verdana, arial, helvetica, sans-serif;
}

#foo {
max-width: 40em;
padding: 1em;
margin: auto;
border: 1px solid #999;
background-color: #fff;
}
</style>

</head>
<body>

<div id="foo" contenteditable>&nbsp;</div>

<button id="but">manipulate input</button>

<script>
(function( d ) {
'use strict';
var foo = d.getElementById( 'foo' ),
but = d.getElementById( 'but' );
but.addEventListener( 'click',
function(){
foo.innerHTML = foo.textContent
.replace( /\&nbsp;/, '' )
.replace( /\&lt;/g, '<' )
.replace( /\&gt;/g, '>' ) ;
}, false );

}( document ));
</script>

</body>
</html>


birdbrain