I have a contenteditable div which contains typical wysiwyg editor html (bold, anchors, lists).
I need to determine if the current cursor is, onKeyDown, at the start and at the end of the div. The reason for this is, based on the cursor position, and the key pressed, I might want to merge this div with the previous div on a backspace, or create a new following div on enter.
I've been fiddling around with ranges, but when you're working with html inside the element, things get pretty complicated.
I'm hoping I must be overlooking some simple solution.
Is there a relatively simple way to determine this - I'm open to using a library like Rangy.
Thanks!
Edit: I'm thinking something along these lines:
$('.mycontenteditable').bind('keydown', handle_keydown)
handle_keydown = function(e) {
range = window.getSelection().getRangeAt(0)
start_range = document.createRange()
start_range.selectNodeContents(this.firstChild)
start_range.collapse(true) // collapse to start
is_start = start_range.compareBoundaryPoints(Range.START_TO_START,range)
end_range = document.createRange()
end_range.selectNodeContents(this.lastChild)
end_range.collapse(false)
is_end = end_range.compareBoundaryPoints(Range.END_TO_END,range)
}
Am I going to run into any odd issues with something like this?
I would use a similar approach to yours except using the toString()
method of Range
objects rather than cloneContents()
to avoid unnecessary cloning. Also, in IE < 9 (which doesn't support ranges), you can use a similar approach with the text
property of TextRange
.
Note that this will have issues when there are leading and/or trailing line breaks in the content because the toString()
method of a range works just like the textContent
property of a node and only considers text nodes, therefore not taking into account line breaks implied by <br>
or block elements. Also CSS is not taken into account: for example, text inside elements that are hidden via display: none
is included.
Here's an example:
Live demo: http://jsfiddle.net/YA3Pu/1/
Code:
function getSelectionTextInfo(el) {
var atStart = false, atEnd = false;
var selRange, testRange;
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
selRange = sel.getRangeAt(0);
testRange = selRange.cloneRange();
testRange.selectNodeContents(el);
testRange.setEnd(selRange.startContainer, selRange.startOffset);
atStart = (testRange.toString() == "");
testRange.selectNodeContents(el);
testRange.setStart(selRange.endContainer, selRange.endOffset);
atEnd = (testRange.toString() == "");
}
} else if (document.selection && document.selection.type != "Control") {
selRange = document.selection.createRange();
testRange = selRange.duplicate();
testRange.moveToElementText(el);
testRange.setEndPoint("EndToStart", selRange);
atStart = (testRange.text == "");
testRange.moveToElementText(el);
testRange.setEndPoint("StartToEnd", selRange);
atEnd = (testRange.text == "");
}
return { atStart: atStart, atEnd: atEnd };
}
This is how I ended up solving this. My proposed solution above worked sometimes but there were way to many edge cases, so I ended up considering how much text was before or after the cursor, and if that was 0 characters, then I was at the start or end:
handle_keydown = function(e) {
// Get the current cusor position
range = window.getSelection().getRangeAt(0)
// Create a new range to deal with text before the cursor
pre_range = document.createRange();
// Have this range select the entire contents of the editable div
pre_range.selectNodeContents(this);
// Set the end point of this range to the start point of the cursor
pre_range.setEnd(range.startContainer, range.startOffset);
// Fetch the contents of this range (text before the cursor)
this_text = pre_range.cloneContents();
// If the text's length is 0, we're at the start of the div.
at_start = this_text.textContent.length === 0;
// Rinse and repeat for text after the cursor to determine if we're at the end.
post_range = document.createRange();
post_range.selectNodeContents(this);
post_range.setStart(range.endContainer, range.endOffset);
next_text = post_range.cloneContents();
at_end = next_text.textContent.length === 0;
}
Still not entirely sure there are any other edge cases, as I'm not entirely sure how to unit test this, since it requires mouse interaction - there's probably a library to deal with this somewhere.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With