I have a contentEditable DIV and, when the user presses a key, the underlying code is processed and replaced by updated code. Alas, this causes the cursor position to be lost.
However, in order to preserve the cursor position, I am successfully inserting a <span id="placeholder"></span> into the DIV at the correct position before processing begins. This preserves the cursor's intended position, but now I can't seem to set the range to select it.
Here's what I currently have:
function focusOnPlaceholder() {
var placeholder = document.getElementById('placeholder');
if( !placeholder ) return;
var sel, range;
if (window.getSelection && document.createRange) {
range = document.createRange();
range.selectNodeContents(placeholder);
range.collapse(true);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(placeholder);
range.select();
}
}
Any help would be appreciated, and a cross-browser solution would be incredible :)
A cross-browser solution would be to use my Rangy library and specifically the selection save/restore module, which uses a similar placeholder technique and is well tested. However, this can probably be fixed without using a library by putting some content (for example, a non-breaking space (\u00A0 or in HTML) inside your placeholder element. You may want to remove the placeholder in focusOnPlaceholder() after selecting the range.
Related
I've been trying to select only the last chars of a ContentEditable Element
I've used an example from Mozilla on http://jsfiddle.net/mh7HK/ which works on an input field but not on a ContentEditable field, although, selecting all does work
This works:
var elem = document.getElementById("contentEdit");
var range = document.createRange();
range.selectNodeContents(elem);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
This doesn't:
var elem = document.getElementById("contentEdit");
var range = document.createRange();
range.setStart(elem, 5) // breaks range Object?
range.setEnd(elem, 8) // breaks range Object?
range.selectNodeContents(elem);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
The .setStart() and .setEnd() should work like that, right?
I've looked at the limited Documentation of jQuery carrot, jQuery selection, Rangy etc., but they all seem to be aimed at getting the selection not setting it.
Two problems:
selectNodeContents(node) moves the range to encompass the whole content of node, so your setStart() and setEnd() calls are essentially ignored.
setStart(node, offset) moves the range's start boundary to offset offset within node. In the case of element, that means placing the boundary after offset child nodes of the element. In the case of a text node, which is probably what you want, the range boundary is placed after offset characters. Same with setEnd(). If you have a single text node within your editable element, you're probably done. Once you start have nested elements, you may want a more subtle approach if you want to set the selection relative to your editable element's text content.
See also https://stackoverflow.com/a/24117242/96100.
I'm having hard time figuring out how to set the caret position based on the last character in the Range object.
For instance I have contenteditable which has some text.
Hello world Test
Then I highlight portion of text ("wor") with mouse:
Then mouseup event will get selected range.
After focus it gone, the selected highlight will disappear.
Now, my goal is to set the caret to between "r" and "l" like image below (the end of the Range)
So far I got getting selection and restoring part worked but can't figure out the caret setting based on Range object.
Code:
var node = this.element.find(".reContentArea")[0];
var range = document.createRange();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
You can use focusOffset to get the character position where the selection ends:
$('.reContentArea').on('mouseup', function() {
var offset= window.getSelection().focusOffset
})
In setting the caret position on a contenteditable element we can use setStart(startNode, startOffset). As you can see in this method we need to specify the node and the offset within that node.
var editable = $('.reContentArea')[0],
range = document.createRange(),
sel = window.getSelection();
range.setStart(editable.firstChild, offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
It's not clear to me what even you want to trigger the "to set the caret to", but I've made a simple click event with this solution on this jsfiddle.
I would recommend using this awesome plugin
https://github.com/acdvorak/jquery.caret
// Select everything after the 6th character
$('input').range(6);
$('textarea').val('Hello\nWorld').range(6).range().text === 'World';
check demo here :
http://www.examplet.org/jquery/caret.php
So I have a method that takes a tag and wraps the selected text in that tag.
function wrap(tag)
{
var sel, range;
if (window.getSelection)
{
sel = window.getSelection();
if (sel.rangeCount)
{
range = sel.getRangeAt(0);
var selectedText = range.toString();
range.deleteContents();
range.insertNode(document.createTextNode('['+tag+']'+selectedText+'[/'+tag+']'));
}
}
}
This issue with this however, is after it's done wrapping the text and inserting the node the caret (where they are typing) is placed BEFORE the inserted text.
Is there such way to insert the text and have the caret remain at the end of it?
Please note i'd prefer if this could be done without the use of jQuery or any other library. I only need it to work in webkit (Safari).
You can use the range.setStartAfter and range.setEndAfter methods to set the start and end points to the point directly after your new node. I setup a jsfiddle example here: http://jsfiddle.net/phil_mcc/tM3mA/
//move the caret
range.setStartAfter(newNode);
range.setEndAfter(newNode);
sel.removeAllRanges();
sel.addRange(range);
do this after inserting the node to the range
range.collapse(false);
this will change position of selection range to the end of the range, so my guess is it should set the cursor at end position
In WebKit innerText seems to return the text of the element that the user sees which is exactly what I need.
Are there any polyfills for Firefox?
For example:
<div id='1'><br> f<div style='display:none'>no</div>oo bar</div>
<script>
function test(){ return document.getElementById('1').innerText }
</script>
The function test would return "\n foo bar".
The goal is to make an editable text area where links are clickable and where tags are highlighted and where the linking and highlighting is created on the fly while typing.
My approach is:
On every keyup:
save the cursor position
cut the text with innerText
parse the links and tags of the text returned by innerText
paste the parsed text into the editable area
restore the cursor position
Thanks!
You can use the toString() method of the Selection object in Firefox, which acts a lot like innerText. Since you're already saving the cursor position before extracting innerText in your example, the following does not bother to save and restore the selection, but otherwise you should be doing that.
function getInnerText(el) {
var text = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
sel.removeAllRanges();
var range = document.createRange();
range.selectNodeContents(el);
sel.addRange(range);
text = sel.toString();
sel.removeAllRanges();
}
return text;
}
Firefox may implement innerText since Aryeh Gregor is writing a specification for innerText.
See http://aryeh.name/spec/innertext/innertext.html and http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-February/030179.html
I'm attempting to create a study tool for a page that allows a user to select any text on the page and click a button. This click then formats the selected text with a yellow background. I can make this work inside of a single tag, but if the range of selection is across multiple tags (for instance, the first LI in an unordered list along with half of the second), I have difficulty applying the style. I can't just wrap the selection with a span here unfortunately.
Basically, I want the effects associated with contentEditable and execCommand without actually making anything editable on the page except to apply a background color to the selected text with the click of a button.
I'm open to jQuery solutions, and found this plug-in that seems to simplify the ability to create ranges across browsers, but I was unable to use it to apply any formatting to the selected range. I can see from the console that it's picking up on the selection, but using something like:
var selected = $().selectedText();
$(selected).css("background-color","yellow");
has no effect.
Any help pointing me in the right direction would be greatly appreciated.
The following should do what you want. In non-IE browsers it turns on designMode, applies a background colour and then switches designMode off again.
UPDATE
Fixed to work in IE 9.
function makeEditableAndHighlight(colour) {
sel = window.getSelection();
if (sel.rangeCount && sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
// Use HiliteColor since some browsers apply BackColor to the whole block
if (!document.execCommand("HiliteColor", false, colour)) {
document.execCommand("BackColor", false, colour);
}
document.designMode = "off";
}
function highlight(colour) {
var range, sel;
if (window.getSelection) {
// IE9 and non-IE
try {
if (!document.execCommand("BackColor", false, colour)) {
makeEditableAndHighlight(colour);
}
} catch (ex) {
makeEditableAndHighlight(colour)
}
} else if (document.selection && document.selection.createRange) {
// IE <= 8 case
range = document.selection.createRange();
range.execCommand("BackColor", false, colour);
}
}
As far as I know, you won't be able to apply styling to regular text. You some sort of html element to operate on. You could try wrapping the selected text with a span and then style the span.
$().selectedText().wrap("<span></span>").parent().addClass("wrapped").css({backgroundColor: "Yellow"});
I added a class of "wrapped" as well, so that on subsequent attempts to highlight text, you can remove previous highlights.
$(".wrapped").each(function(){ ($(this).replaceWith( $(this).text() });
Code is untested.