I'm surprised I'm having trouble with this and unable to find an answer. I'm trying to get the text in a contenteditable, from the start of the contenteditable to the users cursor/caret.
Here's a jsFiddle of what I've attempted (click around the contenteditable and watch console.log).
I get the caret location and then I attempt to get the content:
I tried using textContent of the contenteditable which works but if there's content like foo<br>bar it outputs foobar when ideally it should output foo\r\nbar (Note: This is for a chrome extension I have no control over the content of the contenteditable).
innerText works as expected outputting foo\r\nbar, but as can be seen in the jsFiddle once the html in contenteditable gets a little complex the caret position doesn't seem to match the location in innerText and I have trouble outputting up to the caret.
Found some code using the Range interface and modified it to meet my needs in this jsFiddle but had the same problem with <br> as textContent did.
Note: The user will continue typing as I get the content, so looking for something that doesn't break this flow.
Just looking for direction, any quick tips on what I should try?
In your fiddle I replaced the JavaScript content with:
document.querySelector("#edit").addEventListener("click", function(){
var target = document.querySelector('#edit');
var sel = document.getSelection();
if(!sel.toString()) {
var range = document.getSelection().getRangeAt(0);
var container = range.startContainer;
var offset = range.startOffset;
range.setStart(target, 0);
//do your stuff with sel.toString()
console.log(sel.toString());
//restore the range
range.setStart(container, offset);
}
});
Hope this helps.
Edit: since you said
Note: The user will continue typing as I get the content, so looking for something that doesn't break this flow.
I thought that the click event was just an example; getting the text while user is typing implies:
the entry point can't be click event but probably a setInterval function
while user is typing there is no selections, only the caret
To solve the reported bug is enough changing the code as I did, anyway this is only an example to get the result you are interested in; than you have to work on it to achieve the desired behavior for all the possible case of your real scenario.
Related
Given a div that contains a lot of text that may or may not wrap, I'm looking for the x position of the end of the text in said div. This is user generated text so I don't know what's going to be in it, and also, it needs to be XSS safe, so I cannot use <span> elements in my code because a user could write some script that could execute.
The solutions given here are great, but would allow for unsafe scripts to potentially execute in the browser.
find out length of last incomplete line of text in container
Ideally, I would have a method with a signature something like this:
int getPixelsFromLeftMostSideOfDiv(Element e)
this should be XSS safe assuming that the way you put the user text on the screen is XSS safe.
It follows your linked solution's method of appending a span to the div and getting the x position of that span. Ie
var text = document.getElementById('text');
var span = document.createElement('span');
text.appendChild(span);
console.log(span.getBoundingClientRect().left);
JSFiddle attached for you to play with
https://jsfiddle.net/1d29c24d/1/
My code here returns a JavaScript selection object from within an iFrame (the iFrame page is within the same domain, so no xss issue).
What I need to know is the index of the selection within the raw html code (not the dom).
UPDATE:
E.g.
If you have an html doc:
<html><body>ABC</body></html>
And in the UI, the user uses their mouse to select the text 'ABC', I want to be able to use JavaScript to determine the postion of the selected text in the html source. In this case the index of ABC is 13.
UPDATE 2
The reason I'm persisting with this madness, is that I need to create a tool that can revisit a page and pull text based on a selected text the user has identified at an earlier time. The user tells the system where the text is, and the system from that point on uses regular expressions to pull the text. Now, if the dom is not the same as the raw html, and there's no way to pinpoint the selection in the raw html - it's really difficult to know what reg ex to generate. I don't think there's another way around this.
// Returns the raw selection object currently
// selected in the UI
function getCurrentSelection() {
var selection = null;
var iFrame = document.getElementById('uc_iFrameGetPriceData');
try {
if (window.getSelection) { // Gecko
selection = iFrame.contentWindow.getSelection();
}
else { // IE
var iframeDoc = iFrame.contentWindow.document;
if (iframeDoc.selection) {
selection = iframeDoc.selection;
}
else {
selection = iframeDoc.contentWindow.getSelection();
}
}
}
catch (err) {
alert( 'Error: getCurrentSelection() - ' + err.description )
}
return selection;
}
You can access the index and offset of your selection by using selection.anchorOffset and selection.focusOffset.
Take a look at this:
http://help.dottoro.com/ljjmnrqr.php
And here's another well explaned article:
http://www.quirksmode.org/dom/range_intro.html
update to your update: I'm not sure why you're trying to get the index of the raw HTML code. But you can walk the DOM based on the selection kinda like this:
selection.anchorNode.nodeValue.replace(selection.anchorNode.nodeValue.substring(selection.anchorOffset, selection.focusOffset), 'replace value')
Note that it's still possible that anchorOffset is before focusOffset, based on whether you selected the text from left to right or from right to left.
If I understand correctly, you're looking to move around in the DOM. In that case, you can use these methods/properties:
parentNode
getChildNodes()
firstChild and lastChild
...and these links might help:
http://www.codingforums.com/archive/index.php/t-81035.html
http://www.sitepoint.com/forums/showthread.php?t=586034
The fastest way is probably
var node = document.getElementById('myElement');
alert(node.parentNode.indexOf(node));
(Sorry, for some reason the formatting buttons aren't showing up in my "Your Answer" area...)
I would be surprised if that information was available.
No DOM API is going to let you distinguish between
<html><body>ABC</body></html>
and
<html ><body >ABC</body></html>
The index in the raw HTML is different in each case, but the constructed DOM is identical.
You can't do this sensibly: the only possible method is to re-download the page's HTML via Ajax, parse the HTML and match the resulting DOM against the current DOM, which may itself have been altered by JavaScript. Besides, it's not a useful number anyway because once the page has been loaded, the original HTML string simply no longer exists in the DOM so offsets within that string have no meaning in JavaScript. Getting the selection in terms of nodes and offsets is much more sensible.
I have a <textarea> that I want to grow by one row every time the user enters into the last row shown in the <textarea>. Is this possible?
I have seen it done before, but can't figure out how to do it.
Okay, I just created this off the top of my head, so there may be browser compatibility issues. The concept is pretty simple however. Basically, on every keyup event, append a newline to the current text in the textarea element, then check if scrollHeight is greater than offsetHeight. If this is the case, set the elements height to be equal to scrollHeight. After all this, remove the appended newline. Assuming your textarea element has the id ta:
EDIT -- Okay, my original idea caused the cursor position to jump to the end of the text even if the user moved the cursor back, not good. The fix I came up with involves creating an invisible clone of the text area, and inserting the extra newline in that one, as to not disturb the cursor position in the visible one. Not sure I'm enterily comfortable with this method, but it works for now. If there is a better method I'd be happy to hear it. Here's the updated code:
var shadow = document.createElement('textarea');
shadow.className = 'autosize';
shadow.style.visibility = 'hidden';
document.getElementById('ta').onkeyup = function() {
this.parentNode.appendChild(shadow);
shadow.value = this.value + '\n';
if (shadow.scrollHeight > shadow.offsetHeight) {
this.style.height = shadow.scrollHeight + 'px';
}
this.parentNode.removeChild(shadow);
};
And the updated test http://jsfiddle.net/7wezW/1/
Now back to your regular programming...
Works well in Chrome, if someone points out issues in other browsers, I will try work them out.
PS, I should point out that if the user pastes text using just the mouse, the size of the textarea element will not adjust. Should be a trivial issue, but I'll leave it out as not to over-complicate things.
There are code and a demo at http://webdesign.torn.be/tutorials/javascript/prototype/auto-expand-contract-textarea/.
There's also a non-IE6 version that doesn't use frameworks:
http://scrivna.com/blog/2008/04/12/javascript-expanding-textareas/
Yes, there are many plugins for JQuery, such as this one, which comes with a demo: http://james.padolsey.com/javascript/jquery-plugin-autoresize/
Very customizable in terms of maximum height, buffer space, etc.
I am working on an in-place HTML editor, concentrating on Firefox only right now. I have an element inserted where the cursor should be and also have left and right arrows working, but I can't seem to find a way to find:
Start and end of a line for the home and end keys
The next line up or down for the up/down arrows.
I see document.elementFromPoint, but this doesn't get me a Range object. The Range object itself seems rather useless when it comes to using pixel positions.
If you need a to create a range for the element under specific pixel position, you can combine document.elementFromPoint() and document.createRange() and Range.selectNodeContents();
The snippet below would highlight the content of an element at (100,200)
var elem = document.elementFromPoint(100,200);
var r = document.createRange();
var s = window.getSelection()
r.selectNodeContents(elem);
s.removeAllRanges();
s.addRange(r);
I hope this will help you find the final solution.
I'm not sure what you mean by 'start and end of a line'. Are you referring to a line of text on the page, regardless of its containing element? Can you provide a link to your app or an illustrative example page?
IE and Firefox both provide an element.getClientRects() method. However, it only works under certain circumstances and doesn't behave the same from browser to browser. See quirksmode for details.
This method will probably not be helpful in many situations. In the case that you have a P element containing bare text, Firefox returns a collection containing a single element representing the P element. It doesn't tell you anything about the lines of text it contains.
Most of the in-place editors that I've seen work by clicking on an element to edit it. The element is then replaced by a textbox/area and associated save/cancel buttons.
I have some javascript that manipulates html based on what the user has selected. For real browsers the methods I'm using leverage the "Range" object, obtained as such:
var sel = window.getSelection();
var range = sel.getRangeAt(0);
var content = range.toString();
The content variable contains all the selected text, which works fine. However I'm finding that I cannot detect the newlines in the resulting string. For example:
Selected text is:
abc
def
ghi
range.toString() evaluates to "abcdefghi".
Any search on special characters returns no instance of \n \f \r or even \s. If, however, I write the variable out to an editable control, the line feeds are present again.
Does anyone know what I'm missing?
It may be relevant that these selections and manipulations are on editable divs. The same behaviour is apparent in Chrome, FireFox and Opera. Surprisingly IE needs totally different code anyway, but there aren't any issues there, other than it just being IE.
Many thanks.
Editing my post:
Experimenting a bit, I find that sel.toString() returns new lines in contenteditable divs, while range.toString() returns newlines correctly in normal non-editable divs, but not in editable ones, as you reported.
Could not find any explanation for the behaviour though.
This is a useful link http://www.quirksmode.org/dom/range_intro.html
I found at least two other ways, so you may still use the range to find the position of the caret in Mozilla.
One way is to call
var documentFragment = rangeObj.cloneContents ();
which holds an array of childNodes, and any line breaks will show as a node of class "HTMLBRElement".
The other way is to make sure every "br" tag is followed by a newline character (0x0a)!
This won't hurt the HTML content in any visible way, but now all HTML breaks are translated to plain text line breaks as soon as range.toString() is being called!
I hope this helps - even if this topic is very old. (I'm a necromancer anyway already, hehe) :)
Thanks to the OP I was able to do it using window.getSelection() as he suggested. I needed to get the text until the caret position on an InputEvent , which gives me a static range with the inserted text. So I have a range but not necessarily the current selection's range.
function richToPoorText(range){
//Caso base, está colapsado.
console.log(range);
var restoreRange=document.createRange(); //needed for restoring the caret pos.
restoreRange.setStart(range[0].endContainer, range[0].endOffset);
restoreRange.setEnd(range[0].endContainer, range[0].endOffset);
rangeClone=document.createRange();
rangeClone.setStart(__baseEditor,0);
rangeClone.setEnd(range[0].endContainer, range[0].endOffset);
var str;
var sel=window.getSelection();
sel.removeAllRanges();
sel.addRange(rangeClone); //sel does converts the br to newlines
str=sel.toString();
sel.removeAllRanges();
sel.addRange(restoreRange);
return str;
}
Thankyou very much OP. And if someone has the same use case, you can use this snipset
Edit: __baseEditor is a global variable pointing to the editor's main contenteditable div