Getting highlighted text + position in document

J

JE

Hi!


I am snooping around all sorts of websites and faqs, in search of a way to
pick up any selected (mouse-highlighted) text in a HTML page, and store it
in a JavaScript variable.

I have found an excellent site which actually has a great cross-platform
script for doing just this;

http://www.quirksmode.org/js/selected.html

My problem is that I also need the position of the selected text within
the document. I am finishing a Perl program for editing HTML content, and
I'd like to use such a script to submit words/paragraphs back to the
program, requesting HTML formatting tags. E.g. user selects "very
important", clicks the BOLD button. Then JS gets the text and its
position which is submitted to the Perl program, which adds <B></B> tags,
and refreshes the document.

Without the positions, I naturally get all occurrences of "very important"
in the document in boldface...

Please have a look at the two-liner script snippet in the Quirksmode
document. Is there any way to access position info without losing
cross-platform stability?


Your help would be greatly appreciated! I would love to see this idea
work.



Sincerely,

Joakim Knudsen
(e-mail address removed)
Norway
 
Y

Yann-Erwan Perio

JE said:
My problem is that I also need the position of the selected text within
the document. I am finishing a Perl program for editing HTML content, and
I'd like to use such a script to submit words/paragraphs back to the
program, requesting HTML formatting tags. E.g. user selects "very
important", clicks the BOLD button. Then JS gets the text and its
position which is submitted to the Perl program, which adds <B></B> tags,
and refreshes the document.

This is not a simple problem; the position you're looking for in your
Perl program is a text-based one. However, you only have access to a
serialized source on the client, which means that the position defined
in regards of text isn't really an interesting one, and can vary across
user agents.

If it is acceptable to you to have the "bold" part be done client side
(and it should be since you rely on client objects to start the whole
process), then you could go for a client-side solution, using ranges -
submitting the serialized source to the Perl program afterwards, if
history is needed.

Ranges' models differ between IE and W3C models; IE's model is
text-oriented, offering a great deal of useful methods to directly
manipulate the text, while the W3C's one is more node-oriented, thus
requiring more code to achieve similar results (reorganizing, splitting
nodes etc).


---

<pre style="text-align:center">
Trois allumettes une à une allumées dans la nuit
La première pour voir ton visage tout entier
La seconde pour voir tes yeux
La dernière pour voir ta bouche
Et l'obscurité tout entière pour me rappeler tout cela
En te serrant dans mes bras
</pre>

<input type="button" value="Make Bold" onclick="makeBold()">

<script type="text/javascript">
var makeBold = function() {

function getSel() {
if(window.getSelection) return window.getSelection();
else if(typeof document.selection!="undefined")
return document.selection;
}

function getSelectionRange() {
var sel=getSel();
if(sel) {
if(sel.getRangeAt && sel.rangeCount) return sel.getRangeAt(0);
else if(sel.createRange) return sel.createRange();
}
}

function emptySelection(){
var sel=getSel();
if(sel) {
if(sel.empty) sel.empty();
else if(sel.removeAllRanges) sel.removeAllRanges();
}
}

function _makeBold(){
var rng=getSelectionRange();

if(rng) {
if(rng.execCommand) {
rng.execCommand("bold");
} else if(rng.extractContents){
var endc=rng.endContainer;
var action=1; // make bold
var b, src=rng.extractContents();

do {
if(endc.nodeName.toLowerCase()=="b") {
action=0; // unbold
break;
}
} while((endc=endc.parentNode));

if(action==1){
b=document.createElement("b");
b.appendChild(src);
rng.insertNode(b);
} else {
rng.insertNode(
function(nodeList, parent){
parent=parent||document.createDocumentFragment();
for(var ii=0;ii<nodeList.length;ii++)
parent.appendChild(
arguments.callee(
nodeList[ii].childNodes,
nodeList[ii].nodeName.toLowerCase()=="b" ?
null : nodeList[ii--]
)
);
return parent;
}(src.childNodes)
);
}
emptySelection();
}
}
return true;
}

function doNothing() {
return false;
}

return typeof getSel()!="undefined" ? _makeBold : doNothing;
}();
</script>
 
J

JE

Hi!

I looked at your solution, and it worked!
Still, I have trouble understanding your code, with all the DOM tree
manipulations. Plus I need to save the changes in style in a database, so
I would like to do the change at server-side; i.e. inserting <b></b>
tags...


- Joakim



JE said:
My problem is that I also need the position of the selected text within
the document. I am finishing a Perl program for editing HTML content, and
I'd like to use such a script to submit words/paragraphs back to the
program, requesting HTML formatting tags. E.g. user selects "very
important", clicks the BOLD button. Then JS gets the text and its
position which is submitted to the Perl program, which adds <B></B> tags,
and refreshes the document.

This is not a simple problem; the position you're looking for in your
Perl program is a text-based one. However, you only have access to a
serialized source on the client, which means that the position defined
in regards of text isn't really an interesting one, and can vary across
user agents.

If it is acceptable to you to have the "bold" part be done client side
(and it should be since you rely on client objects to start the whole
process), then you could go for a client-side solution, using ranges -
submitting the serialized source to the Perl program afterwards, if
history is needed.

Ranges' models differ between IE and W3C models; IE's model is
text-oriented, offering a great deal of useful methods to directly
manipulate the text, while the W3C's one is more node-oriented, thus
requiring more code to achieve similar results (reorganizing, splitting
nodes etc).


---

<pre style="text-align:center">
Trois allumettes une à une allumées dans la nuit
La première pour voir ton visage tout entier
La seconde pour voir tes yeux
La dernière pour voir ta bouche
Et l'obscurité tout entière pour me rappeler tout cela
En te serrant dans mes bras
</pre>

<input type="button" value="Make Bold" onclick="makeBold()">

<script type="text/javascript">
var makeBold = function() {

function getSel() {
if(window.getSelection) return window.getSelection();
else if(typeof document.selection!="undefined")
return document.selection;
}

function getSelectionRange() {
var sel=getSel();
if(sel) {
if(sel.getRangeAt && sel.rangeCount) return sel.getRangeAt(0);
else if(sel.createRange) return sel.createRange();
}
}

function emptySelection(){
var sel=getSel();
if(sel) {
if(sel.empty) sel.empty();
else if(sel.removeAllRanges) sel.removeAllRanges();
}
}

function _makeBold(){
var rng=getSelectionRange();

if(rng) {
if(rng.execCommand) {
rng.execCommand("bold");
} else if(rng.extractContents){
var endc=rng.endContainer;
var action=1; // make bold
var b, src=rng.extractContents();

do {
if(endc.nodeName.toLowerCase()=="b") {
action=0; // unbold
break;
}
} while((endc=endc.parentNode));

if(action==1){
b=document.createElement("b");
b.appendChild(src);
rng.insertNode(b);
} else {
rng.insertNode(
function(nodeList, parent){
parent=parent||document.createDocumentFragment();
for(var ii=0;ii<nodeList.length;ii++)
parent.appendChild(
arguments.callee(
nodeList[ii].childNodes,
nodeList[ii].nodeName.toLowerCase()=="b" ?
null : nodeList[ii--]
)
);
return parent;
}(src.childNodes)
);
}
emptySelection();
}
}
return true;
}

function doNothing() {
return false;
}

return typeof getSel()!="undefined" ? _makeBold : doNothing;
}();
</script>

---


HTH
Yep.
 
Y

Yann-Erwan Perio

JE said:
Plus I need to save the changes in style in a database, so
I would like to do the change at server-side; i.e. inserting <b></b>
tags...

I can understand the feeling, however doing the change server-side would
be highly difficult, since the position has some sense on the client
only, in the DOM. You could of course do some text analysis on a
serialized DOM (say, obtained by innerHTML) also taking into account
nesting issues, or transmit a node/offset object then rebuild the
position server-side using a DOM parser, but as you can see this would
be a real pain to implement.

Actually, you don't need to do the change server-side to keep the
history; once the user has highlighted the text as required, he just has
to submit a form which will retrieve the source client-side (be it the
serialized source from innerHTML or, maybe better, a manually built
source from the DOM tree) and post it to the server.


Slightly tested only:

---
<pre id="text">
<b>He<i>ll</i>o</b>, World!
</pre>

<form action="foo" method="post" onsubmit="return saveChanges(this)">
<input type="hidden" name="textModified" value="">
<input type="submit" value="Save changes">
</form>

<script type="text/javascript">
function saveChanges(frm){
if(document.getElementById &&
document.body &&
document.body.childNodes &&
typeof document.body.nodeValue!="undefined") {

var root=document.getElementById("text");
var txt=[];
var getHTML = function(node) {
var buf=[];
if(node.nodeType==1){
buf.push("<"+node.nodeName+">");
for(var ii=0; ii<node.childNodes.length; ii++)
buf.push(arguments.callee(node.childNodes[ii]));
buf.push("<\/"+node.nodeName+">");
} else if(node.nodeType==3){
buf.push(node.nodeValue);
}
return buf.join("");
}

//frm.elements["textModified"].value=root.innerHTML;
for(var j=0; j<root.childNodes.length; j++)
txt.push(getHTML(root.childNodes[j]));
frm.elements["textModified"].value=txt.join("");
return true;
}
return false;
}
</script>
---
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,012
Latest member
RoxanneDzm

Latest Threads

Top