I have a contenteditable div and it contains other tags and not only plain text. Only one # is allowed in. How can I get the range of the characters between # and caret if such a range exists?
Ha that was easier than I thought!. Based on this easy to overlook question: Div "contenteditable" : get and delete word preceding caret I forked its jsfiddle and here is mine working as expected:
http://jsfiddle.net/52m2thu2/1/
function getWordBetweenAtAndCaret(containerEl) {
var preceding = "",
sel,
range,
precedingRange;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(containerEl, 0);
preceding = range.toString();
}
} else if ((sel = document.selection) && sel.type != "Control") {
range = sel.createRange();
precedingRange = range.duplicate();
precedingRange.moveToElementText(containerEl);
precedingRange.setEndPoint("EndToStart", range);
preceding = precedingRange.text;
}
var lastWord = preceding.match(/#(.+)$/i);
if (lastWord) {
return lastWord;
} else {
return false;
}
}
Related
I have a html tag which is
<span>This is first text<span class="ignore">Second</span> This is third text<span>
I am trying to get the start and end index from the selected text. When I select third I get start and end index as 34 39
But I expect 27 32
I tried the below approach
export const findTextRange = (element) => {
if (!element) return;
let start = 0, end = 0;
let sel, range, priorRange, text;
if (typeof window.getSelection != "undefined") {
sel = window.getSelection();
text = sel + '';
if (window.getSelection().rangeCount <= 0) {
return;
}
range = window.getSelection().getRangeAt(0);
priorRange = range.cloneRange();
priorRange.selectNodeContents(element);
priorRange.setEnd(range.startContainer, range.startOffset);
start = priorRange.toString().length;
end = start + (sel + '').length;
} else if (typeof document.selection !== "undefined" &&
(sel = document.selection).type !== "Control") {
text = sel + '';
range = sel.createRange();
priorRange = document.body.createTextRange();
priorRange.moveToElementText(element);
priorRange.setEndPoint("EndToStart", range);
start = priorRange.text.length;
end = start + (sel + '').length;
}
return { start, end, text };
}
Is there any way where I can ignore the span element with ignore class.
Store the initial HTML, then remove all elements having the .ignore class:
const html = element.innerHTML;
element.querySelectorAll('.ignore').forEach((e) => e.remove());
After getting the range, restore the original HTML:
element.innerHTML = html;
Snippet
const findTextRange = (element) => {
if (!element) return;
const html = element.innerHTML; // store original HTML
element.querySelectorAll('.ignore').forEach((e) => e.remove()); // remove ignore elements
let start = 0, end = 0;
let sel, range, priorRange, text;
if (typeof window.getSelection != "undefined") {
sel = window.getSelection();
text = sel + '';
if (window.getSelection().rangeCount <= 0) {
return;
}
range = window.getSelection().getRangeAt(0);
priorRange = range.cloneRange();
priorRange.selectNodeContents(element);
priorRange.setEnd(range.startContainer, range.startOffset);
start = priorRange.toString().length;
end = start + (sel + '').length;
} else if (typeof document.selection !== "undefined" &&
(sel = document.selection).type !== "Control") {
text = sel + '';
range = sel.createRange();
priorRange = document.body.createTextRange();
priorRange.moveToElementText(element);
priorRange.setEndPoint("EndToStart", range);
start = priorRange.text.length;
end = start + (sel + '').length;
}
element.innerHTML = html; // restore HTML
console.log(start, end, text);
return { start, end, text };
}
document.querySelector('#P').addEventListener('click', function() {findTextRange(this)});
<span id="P">This is first text<span class="ignore">Second</span> This is third text<span>
I'm currently making a WYSIWYG editor but having some problems. I'm trying to add links in but when I go to add a link it takes the focus away from the div as the user must type the link in a text box.
I've got a function that gets the last position of the cursor:
<div id="editor" contenteditable="true"></div>
function getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
var update = function() {
getCaretCharacterOffsetWithin(this);
};
$('#editor').on("mousedown mouseup keydown keyup", update);
Is there a way to ExecCommand at the caret point?
EDIT: Added a JSFiddle to see how things work - https://jsfiddle.net/hju3bLyx/2/
you need to save the selection when editor lost focus
var savedSel;
function createLink() {
$('#editor').focus();
var url = document.getElementById("url").value;
restoreSelection(savedSel);
document.execCommand("CreateLink", false, url);
}
// it saved here
$('#editor').focusout(function(){
savedSel = saveSelection();
})
I would like to create a function that select a given text inside a HTML element.
For example calling selectText('world') would select world in a markup like <span>Hello </span><strong>world</strong>!
Lots of answers on similar questions suggests to use range and selection but none of them work in my case (some would select all the text, some won't work with such markup, ...).
For now this is what I have (it doesn't work):
function selectText ( element, textToSelect ) {
var text = element.textContent,
start = text.indexOf( textToSelect ),
end = start + textToSelect.length - 1,
selection, range;
element.focus();
if( window.getSelection && document.createRange ) {
range = document.createRange();
range.setStart( element.firstChild, start );
range.setEnd( element.lastChild, end );
selection = window.getSelection();
selection.removeAllRanges();
selection.addRange( range );
} else if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText( element );
range.moveStart( 'character', start );
range.collapse( true );
range.moveEnd( 'character', end );
range.select();
}
}
Here is a jsfiddle so you see what is actually happening: http://jsfiddle.net/H2H2p/
Outputed error :
Uncaught IndexSizeError: Failed to execute 'setStart' on 'Range': The offset 11 is larger than or equal to the node's length (5).
P.S.: no jQuery please :)
You could use a combination of your approach of finding the text within the element's textContent and this function.
Demo: http://jsfiddle.net/H2H2p/3/
Code:
function selectText(element, textToSelect) {
var elementText;
if (typeof element.textContent == "string" && document.createRange && window.getSelection) {
elementText = element.textContent;
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElement(element);
elementText = textRange.text;
}
var startIndex = elementText.indexOf(textToSelect);
setSelectionRange(element, startIndex, startIndex + textToSelect.length);
}
function getTextNodesIn(node) {
var textNodes = [];
if (node.nodeType == 3) {
textNodes.push(node);
} else {
var children = node.childNodes;
for (var i = 0, len = children.length; i < len; ++i) {
textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
}
}
return textNodes;
}
function setSelectionRange(el, start, end) {
if (document.createRange && window.getSelection) {
var range = document.createRange();
range.selectNodeContents(el);
var textNodes = getTextNodesIn(el);
var foundStart = false;
var charCount = 0, endCharCount;
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
endCharCount = charCount + textNode.length;
if (!foundStart && start >= charCount
&& (start < endCharCount ||
(start == endCharCount && i < textNodes.length))) {
range.setStart(textNode, start - charCount);
foundStart = true;
}
if (foundStart && end <= endCharCount) {
range.setEnd(textNode, end - charCount);
break;
}
charCount = endCharCount;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(true);
textRange.moveEnd("character", end);
textRange.moveStart("character", start);
textRange.select();
}
}
I have a textarea with a lot of text in it, and I have code so that when you press Shift+Enter, it will insert a piece of text. However, at the moment, as soon as that happens, the text scrolls so that the carets at the bottom of the screen.
My insert code:
$("#body textarea").bind('keydown', function(event) {
var caret = $("#body textarea").caret();
if(event.keyCode == 13 && event.shiftKey)
{
var text = "[br]"
insertText("#body textarea", caret.start, caret.end, text, "");
$("#body textarea").caret(caret.start+(text.length), caret.start+(text.length));
}
});
Does anyone know what I can do to stop it forcing the caret to the bottom?
Cheers
BlackWraith
I found on stackoverflow.com same trouble and make sample for you
http://jsfiddle.net/deerua/WAZBQ/
You can set the Caret Position manually after inserting your text:
See http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/:
function doGetCaretPosition (ctrl) {
var CaretPos = 0; // IE Support
if (document.selection) {
ctrl.focus ();
var Sel = document.selection.createRange ();
Sel.moveStart ('character', -ctrl.value.length);
CaretPos = Sel.text.length;
}
// Firefox support
else if (ctrl.selectionStart || ctrl.selectionStart == '0')
CaretPos = ctrl.selectionStart;
return (CaretPos);
}
function setCaretPosition(ctrl, pos){
if(ctrl.setSelectionRange)
{
ctrl.focus();
ctrl.setSelectionRange(pos,pos);
}
else if (ctrl.createTextRange) {
var range = ctrl.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
}
ctrl is your textarea-element.
setCaretPosition(document.getElementById("textarea", 0);
Is there anyway to check if the character at the cursor in TEXTAREA is a "space"? If it is, return TRUE. Let me know how to do this using jQuery.
Thanks
This works in recent versions of the main browsers and has the added bonus of not requiring jQuery or any other library:
function nextCharIsSpace(textArea) {
var selectedRange, range, selectionEndIndex;
// Non-IE browsers
if (typeof textArea.selectionEnd == "number") {
selectionEndIndex = textArea.selectionEnd;
}
// IE is more complicated
else if (document.selection && document.selection.createRange) {
textArea.focus();
selectedRange = document.selection.createRange();
range = selectedRange.duplicate();
range.moveToElementText(textArea);
range.setEndPoint("EndToEnd", selectedRange);
selectionEndIndex = range.text.length;
}
return textArea.value.charAt(selectionEndIndex) === " ";
}