Link to home
Start Free TrialLog in
Avatar of CPL593H
CPL593HFlag for Belgium

asked on

Javascript getRangeAt(0) and childNodes problems.

Hi experts,

Consider the following example.

I'm trying to get information about the position of the start and end offset of a text selection. (I can infer the line number easily, that's not the issue here).

The problem occurs when I'm using text enrichments such as <strong> <em> and <span> (used for colorizing).

If you try the script after selecting a part of the text that has no <strong> <em> or <span>, you get the "correct" start and end offset.

If you try the same with one line where one of these tags is used, it fails, because a <strong> <em> or <span> creates a new childNode. The offset is counted from the new node, not from the start of the line.

From a Javascript/DOM, it makes sense, of course. But for what I want to do, it is a major problem.

So my question is: is it possible to get these offset, while ignoring <strong> <em> and <span>'s (but not their contents, of course)?
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
      "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
	<title></title>
</head>
<body>
<script type="text/javascript">
<!--
 
function test() {
	var range = window.getSelection().getRangeAt(0);
	var startOffset = range.startOffset;
	var endOffset = range.endOffset;
	alert ('startOffset: '+startOffset+'\nendOffset: '+endOffset);
}
 
//-->
</script>
<p><a href="javascript:test();">Select a part of Romeo and Juliet then click to test</a></p>
<p>Sampson. Gregory, o' my word, we'll not carry coals.<br />
Gregory. Gregory, o' my word, we'll not carry coals.<br />
Gregory. No, for then we should be colliers.<br />
Sampson. I mean, an we be in choler, we'll draw.<br />
Gregory. Ay, while you live, draw your neck out o' the collar.<br />
Sampson. I strike quickly, being moved.<br />
<strong>Gregory.</strong> But thou art not quickly moved to strike. &lt;-- problem 1<br />
Sampson. A dog of the house of Montague moves me.<br />
Gregory. <em>To move is to stir</em>; and to be valiant is to stand: &lt;-- problem 2<br />
therefore, if thou art moved, thou runn'st away.<br />
Sampson. <span style="color: red;">A dog of that house shall</span> move me to stand: I will &lt;-- problem 3<br />
take the wall of any man or maid of Montague's.<br />
Gregory. That shows thee a weak slave; for the weakest goes<br />
to the wall.<br />
Sampson. True; and therefore women, being the weaker vessels, 30<br />
are ever thrust to the wall: therefore I will push<br />
Montague's men from the wall, and thrust his maids<br />
to the wall.<br />
Gregory. The quarrel is between our masters and us their men.<br />
Sampson. 'Tis all one, I will show myself a tyrant: when I 35<br />
have fought with the men, I will be cruel with the<br />
maids, and cut off their heads.<br />
Gregory. The heads of the maids?<br />
Sampson. Ay, the heads of the maids, or their maidenheads;<br />
take it in what sense thou wilt.<br />
Gregory. They must take it in sense that feel it.<br />
Sampson. Me they shall feel while I am able to stand: and<br />
'tis known I am a pretty piece of flesh.<br />
Gregory. 'Tis well thou art not fish; if thou hadst, thou<br />
hadst been poor John. Draw thy tool! here comes<br />
two of the house of the Montagues.<br />
Sampson. My naked weapon is out: quarrel, I will back thee.<br />
Gregory. How! turn thy back and run?<br />
Sampson. Fear me not.<br />
Gregory. No, marry; I fear thee!</p>
</body>
</html>

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of abel
abel
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of CPL593H

ASKER

Abel, thanks for your answer. However, I have already tried to get around the problem by counting the length thanks to toString(), but each try was a failure.

In fact with your suggestion, two problems remain:

- the obvious one, I still don't know the start offset;
- in this case, the end offset is not computed according to the start of the line (after the ), but to the start of the paragraph (after the initial ).

But maybe by doing some trickery that may be obvious to some of us, we can achieve something.
Do I understand you correctly that you want to find out the offset from the start of the line and that you define a "line" as anything following a <br /> element? Because if so, I may come up with something that would work.

If you plan to use this in an environment where the line is defined as part of word-wrapped text, it will be much harder, perhaps impossible, to achieve with DOM L2 means.
Avatar of CPL593H

ASKER

In fact I simplified it a bit for readability. You can see the real thing in the code snippet below (with anonymized text).

It's a table with four rows, where only the third row selectable. The third row is 80-char max text.

The first row is the line number, the second, the paragraph number and the fourth contains short comments. The comments are inserted by finding the start line, the start offset, the end line and the end offset.

The rows 1, 2 and 4 are made not selectable thanks to this function (therefore they don't interfere):

function disableSelection(element) {
      element.onselectstart = function() {
            return false;
      };
      element.unselectable = "on";
      element.style.MozUserSelect = "none";
      element.style.cursor = "default";
      element.style.webkitUserSelect = "none";
}

Suppose that in this example, "dolor sit amet, consectetur adipiscing elit." is related to comment 1. I'd give the snippet a background color corresponding to the color of the type of "comment 1". For that, I'd enclose the snippet in . But if I want to be able to comment "Vestibulum nisi tellu", that comes right after, I won't find the correct starting end ending offsets once the first snippet is colorized.

(I'm sorry if I'm not very clear in this explanation. It's a pretty simple thing to use, it's another thing to put the right words on it.)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
      "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
	<title></title>
</head>
<body>
<table>
<tr class="altsegment">
<td>1</td>
<td>[s1]</td>
<td class="selectable"><span id="l1-s1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nisi tellus,</span></td>
<td style="width: 16%;"><span id="c1">comment1</span></td>
 
</tr>
<tr class="altsegment">
<td>2</td>
<td></td>
<td class="selectable"><span id="l2-s1">luctus in, malesuada vitae, auctor ut, tortor. Mauris at leo. Maecenas pretium</span></td>
<td style="width: 16%;"><span id="c2"></span></td>
</tr>
<tr class="altsegment">
 
<td>3</td>
<td></td>
<td class="selectable"><span id="l3-s1">porta dolor. Donec id justo id mi varius consectetur. Morbi diam. Cras lectus.</span></td>
<td style="width: 16%;"><span id="c3">comment2</span></td>
</tr>
<tr class="altsegment">
<td>4</td>
 
<td></td>
<td class="selectable"><span id="l4-s1">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac</span></td>
<td style="width: 16%;"><span id="c4"></span></td>
</tr>
<tr class="altsegment">
<td>5</td>
<td></td>
<td class="selectable"><span id="l5-s1">turpis egestas. Cras elementum mi. Aenean laoreet fermentum risus. Fusce neque</span></td>
 
<td style="width: 16%;"><span id="c5"></span></td>
</tr>
<tr class="altsegment">
<td>6</td>
<td></td>
<td class="selectable"><span id="l6-s1">elit, placerat in, cursus et, accumsan et, ipsum. Vestibulum viverra lobortis</span></td>
<td style="width: 16%;"><span id="c6"></span></td>
</tr>
 
<tr class="altsegment">
<td>7</td>
<td></td>
<td class="selectable"><span id="l7-s1">magna. Nunc non mi. Suspendisse vitae tortor.</span></td>
<td style="width: 16%;"><span id="c7"></span></td>
</tr>
<tr>
<td>8</td>
 
<td>[s2]</td>
<td class="selectable"><span id="l8-s2">Maecenas justo. Ut eu mi at urna dapibus gravida. Sed mollis nisl ac libero.</span></td>
<td style="width: 16%;"><span id="c8"></span></td>
</tr>
<tr>
<td>9</td>
<td></td>
 
<td class="selectable"><span id="l9-s2">Quisque facilisis faucibus lorem. Duis enim. Curabitur augue diam, vehicula</span></td>
<td style="width: 16%;"><span id="c9"></span></td>
</tr>
<tr>
<td>10</td>
<td></td>
<td class="selectable"><span id="l10-s2">vitae, tincidunt vel, tincidunt vel, nunc. Aliquam in lorem sit amet leo</span></td>
 
<td style="width: 16%;"><span id="c10">comment3</span></td>
</tr>
<tr>
<td>11</td>
<td></td>
<td class="selectable"><span id="l11-s2">adipiscing eleifend. Suspendisse arcu augue, accumsan sit amet, posuere quis,</span></td>
<td style="width: 16%;"><span id="c11"></span></td>
</tr>
 
<tr>
<td>12</td>
<td></td>
<td class="selectable"><span id="l12-s2">porttitor non, ante. Mauris a tellus. Nulla facilisi. Duis erat. Nullam</span></td>
<td style="width: 16%;"><span id="c12"></span></td>
</tr>
<tr>
<td>13</td>
 
<td></td>
<td class="selectable"><span id="l13-s2">fermentum nisi vitae sapien. Phasellus lobortis dictum est. Cras a dui id risus</span></td>
<td style="width: 16%;"><span id="c13"></span></td>
</tr>
<tr>
<td>14</td>
<td></td>
<td class="selectable"><span id="l14-s2">pharetra vestibulum. Nulla viverra massa nec ipsum. Aliquam erat volutpat.</span></td>
 
<td style="width: 16%;"><span id="c14"></span></td>
</tr>
<tr>
<td>15</td>
<td></td>
<td class="selectable"><span id="l15-s2">Quisque egestas, odio pharetra pulvinar aliquet, tellus erat venenatis sem, et</span></td>
<td style="width: 16%;"><span id="c15"></span></td>
</tr>
 
<tr>
<td>16</td>
<td></td>
<td class="selectable"><span id="l16-s2">gravida lectus erat nec ante. Phasellus rhoncus fermentum ante. Integer</span></td>
<td style="width: 16%;"><span id="c16">comment4</span></td>
</tr>
<tr>
<td>17</td>
 
<td></td>
<td class="selectable"><span id="l17-s2">adipiscing fringilla leo. Quisque et enim sit amet tellus porttitor auctor.</span></td>
<td style="width: 16%;"><span id="c17"></span></td>
</tr>
<tr class="altsegment">
<td>18</td>
<td>[s3]</td>
 
<td class="selectable"><span id="l18-s3">Duis sed nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque</span></td>
<td style="width: 16%;"><span id="c18"></span></td>
</tr>
<tr class="altsegment">
<td>19</td>
<td></td>
<td class="selectable"><span id="l19-s3">lacus. Praesent malesuada tristique orci. In bibendum magna mollis odio.</span></td>
 
<td style="width: 16%;"><span id="c19"></span></td>
</tr>
<tr class="altsegment">
<td>20</td>
<td></td>
<td class="selectable"><span id="l20-s3">Pellentesque et purus. Nam eget eros sit amet purus ultrices commodo. Integer</span></td>
<td style="width: 16%;"><span id="c20"></span></td>
</tr>
 
<tr class="altsegment">
<td>21</td>
<td></td>
<td class="selectable"><span id="l21-s3">feugiat tellus. Sed vehicula mi vel purus. Mauris a velit non enim vulputate</span></td>
<td style="width: 16%;"><span id="c21">comment5</span></td>
</tr>
<tr class="altsegment">
<td>22</td>
 
<td></td>
<td class="selectable"><span id="l22-s3">fermentum. Praesent sapien. Quisque dapibus blandit ipsum. Fusce volutpat</span></td>
<td style="width: 16%;"><span id="c22"></span></td>
</tr>
<tr class="altsegment">
<td>23</td>
<td></td>
<td class="selectable"><span id="l23-s3">commodo enim. Pellentesque ac mi id arcu placerat condimentum.</span></td>
 
<td style="width: 16%;"><span id="c23"></span></td>
</tr>
</table>
</body>
</html>

Open in new window

Avatar of CPL593H

ASKER

The EE parser ate a part of a sentence:

"For that, I'd enclose the snippet in ." should read For that, I'd enclose the snippet in [span class="red"][/span]. The EE parser doesn't like greater than and lesser than signs. :)
Hold on, do I understand you correctly that you want to insert some content at the end or start of the selection? Because that is much easier to do! You can use something like the following (inserts at the beginning):

var sel = window.getSelection();var range = sel.getRangeAt(0);var elem = document.createElement("span");elem.appendChild(document.createTextNode(" some new content here "));range.insertNode(elem);
is that what you were looking for, or do I get it totally backward? ;)
Avatar of CPL593H

ASKER

In fact I think you sort of just answered my second question (also open on EE, take a look at it): for now, in my test code, I insert the spans at the stored offsets server-side, and would like to do that dynamically (as you suggest it).

Back to our original question, that is about a preliminary step, where I compute the start offset and end offsets and store them in a database for later retrieval.

If I select "Donec id justo id mi varius consectetur. Morbi diam. Cras lectus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." is my lorem ipsum example, it will do the following INSERT in my db:

INSERT INTO `comments` (`id`, `id_text`, `id_segment_start`, `id_segment_end`, `id_user`, `comment`, `offset_start`, `offset_end`, `line_start`, `line_end`, `date`) VALUES
(NULL, 5, 1, 1, 1, 'loremtest', 13, 15, 3, 5, '2009-04-19 21:35:20');

Most of what is relevant to us is the coordinates: the comment on the text number 5 starts on segment (paragraph) 1, ends on paragraph 1, starts at the offset 13 of line 3 end ends at the offset 15 of line 5.

I can't get these values right if the text is "polluted" by other comments' spans. Hence my original question.



Avatar of CPL593H

ASKER

Abel, do you think you can help?

Thanks
Yes, I think so. I think... that we should think out of the box, to make things less error-prone. If I understand your system well, you want to create something that allows you to place comments inside existing html data. That data already contains markup.

What if you would actually do the following:

  1. The comments are stored separately (as you do now)
  2. When the user selects some parts of mixed content, you embrace it (with a span), using the technique mentioned earlier
  3. You use a special tag or id which links to the comment
  4. Now you can decide to use javascript to display the comments, you should not put the comments in the literal text, because that will make future selections problematic
  5. When you are done, you should wrap the embracing-span tag so that it becomes an empty span tag at the beginning (or end) of the selected text.
The method you used so far doesn't seem to go down a reliable path (imho). If you are willing to think a bit further and open your mind for other options, this new path may proof easier to implement and more reliable to use.

-- Abel --
Avatar of CPL593H

ASKER

Hello Abel,

For now, there is no markup in my third column (the one that matters). The only markup that will ever be needed to be placed there will be an opening span at the start of the marked text and a closing span at its end. In fact, if a comment encompasses more than one line, a span will have to be opened on each encompassed line.

I know there are a lot of shortcomings on my method, that's why a bit of out-of-the-box thinking is more than welcome. I'm at a point where I still can change most of the implementation.

So, let's cut it in parts. Consider the very simplified example below.

Step 1: just the text. I select the parts I want to tag with the mouse, the coordinates are sent to the database (see an older post for details). Storing the coordinates to the db is the only step that has to be done like it is now.

Step 2: I want to tag "comment1" the snippet "consectetur adipiscing elit" on line 1. I select the snippet, click a button. The computer finds four coordinates (the start and end lines (trivial), the start end end offset (less trivial). It dynamically adds a [span class="redbckg"][/span] around the snippet. Next time the page is loaded, they are added via Javascript on load. The tag "comment1" itself is on the fourth column, that is not selectable.

Step 3: I want "comment2" for "Maecenas pretium porta dolor. Donec id justo id mi varius consectetur. Morbi diam. Cras lectus. Pellentesque habitant morbi tristique senectus". Same thing, except that we begin on line 2 at "Maecenas" (offset 70 or so), that we add a span to all of line 3 and to line 4 until the end of "senectus" (offset 50 or so).

Step 4: a bit harder. I want "comment3" for "varius consectetur" (line 3, middle). But there's already a [span class="greenbckg"] that encompasses all the line for the "comment2". The script I use to get coordinates would fail there.

I'm not sure this is very clear, if not, I'll give you an access to the on-line prototype so you can see what it does. It is really simpler than my long explanations.
STEP 1: Just the text
 
<table>
<tr class="altsegment">
<td>1</td>
<td>[s1]</td>
<td class="selectable"><span id="l1-s1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nisi tellus,</span></td>
<td style="width: 16%;"><span id="c1"></span></td>
 
</tr>
<tr class="altsegment">
<td>2</td>
<td></td>
<td class="selectable"><span id="l2-s1">luctus in, malesuada vitae, auctor ut, tortor. Mauris at leo. Maecenas pretium</span></td>
<td style="width: 16%;"><span id="c2"></span></td>
</tr>
<tr class="altsegment">
 
<td>3</td>
<td></td>
<td class="selectable"><span id="l3-s1">porta dolor. Donec id justo id mi varius consectetur. Morbi diam. Cras lectus.</span></td>
<td style="width: 16%;"><span id="c3"></span></td>
</tr>
<tr class="altsegment">
<td>4</td>
 
<td></td>
<td class="selectable"><span id="l4-s1">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac</span></td>
<td style="width: 16%;"><span id="c4"></span></td>
</tr>
<tr class="altsegment">
<td>5</td>
<td></td>
<td class="selectable"><span id="l5-s1">turpis egestas. Cras elementum mi. Aenean laoreet fermentum risus. Fusce neque</span></td>
 
<td style="width: 16%;"><span id="c5"></span></td>
</tr>
<tr class="altsegment">
<td>6</td>
<td></td>
<td class="selectable"><span id="l6-s1">elit, placerat in, cursus et, accumsan et, ipsum. Vestibulum viverra lobortis</span></td>
<td style="width: 16%;"><span id="c6"></span></td>
</tr>
</table>
 
STEPS 2 TO 4: A bit more complicated
 
<table>
<tr class="altsegment">
<td>1</td>
<td>[s1]</td>
<td class="selectable"><span id="l1-s1">Lorem ipsum dolor sit amet, <span class="redbckg">consectetur adipiscing elit</span>. Vestibulum nisi tellus,</span></td>
<td style="width: 16%;"><span id="c1"><span class="redbckg">comment1</span></span></td>
 
</tr>
<tr class="altsegment">
<td>2</td>
<td></td>
<td class="selectable"><span id="l2-s1">luctus in, malesuada vitae, auctor ut, tortor. Mauris at leo. <span class="greenbckg">Maecenas pretium</span></span></td>
<td style="width: 16%;"><span id="c2"></span></td>
</tr>
<tr class="altsegment">
 
<td>3</td>
<td></td>
<td class="selectable"><span id="l3-s1"><span class="greenbckg">porta dolor. Donec id justo id mi <span class="bluebckg">varius consectetur</span>. Morbi diam. Cras lectus.</span></span></td>
<td style="width: 16%;"><span id="c3"><span class="greenbckg">comment2</span> <span class="bluebckg">comment3</span></span></td>
</tr>
<tr class="altsegment">
<td>4</td>
 
<td></td>
<td class="selectable"><span id="l4-s1"><span class="greenbckg">Pellentesque habitant morbi tristique senectus</span> et netus et malesuada fames ac</span></td>
<td style="width: 16%;"><span id="c4"></span></td>
</tr>
<tr class="altsegment">
<td>5</td>
<td></td>
<td class="selectable"><span id="l5-s1">turpis egestas. Cras elementum mi. Aenean laoreet fermentum risus. Fusce neque</span></td>
 
<td style="width: 16%;"><span id="c5"></span></td>
</tr>
<tr class="altsegment">
<td>6</td>
<td></td>
<td class="selectable"><span id="l6-s1">elit, placerat in, cursus et, accumsan et, ipsum. Vestibulum viverra lobortis</span></td>
<td style="width: 16%;"><span id="c6"></span></td>
</tr>
</table>

Open in new window

hmmm, how many years have you got to get this correct? And don't forget, you also need to make it working for IE (or don't you?) which doesn't support ranges.

I can only give you some advices along the path. Haven't suggested it yet, but you may, instead of span-elements, use comment elements instead. With a little bit of luck they won't interfere with the range and then you can store the data of the comment in there, including the length (start/end is not necessary).

Complex problems like this are actually best discussed face to face. I see that you are quite well aware of the possibilities, now you only need to find out what the best approach is and a bit of sparring would be nice. If you want that, you can look up my details in my profile and send me a mail with your phone nr. Don't overdo it, we're still volunteers (unless you want to hire me, of course), but I do like the challenge.

-- Abel --
Avatar of CPL593H

ASKER

Hi again,

Thanks for your ideas. IE compatibility is not needed for this project. I've sent you an e-mail with a link to the real thing and a login/password, so you can really see what I'm trying to do. More information in the mail.

Thanks,
Stéphane
Avatar of CPL593H

ASKER

Well, I've changed the behavior of my application. For now, it works, we'll see if we can do better later. :-)