Jquery set cursor at the end of the text in contenteditable - javascript

I am working on my jquery script to remove the link by replace the text when I click on a button. I have got a problem with the cursor at the end of the text because it will move the cursor at the start of the text, example: when I click next to 2!, it will move the cursor at the start before the Video Here 1 when I try this:
selected_text = window.getSelection().getRangeAt(0).endContainer.wholeText;
$('#text_editor').html($('#text_editor').html().replace('Video Here 2!', selected_text));
$('#text_editor').focus().val(selected_text);
Here is my code: https://jsfiddle.net/su9dktrz/
What I want to achieve is when I click on the text "Video Here 1!", "Video Here 2!", "Video Here 3!" or whatever it is and when I click on a button to remove the hyperlink to replace it with a text, I want to move the cusor next to the text 2! or whatever it is.
Can you please show me example how I can move the cursor next to the text 1!, 2!, 3! or whatever it is in the contenteditable?
I have tried to find the answer on google but I couldn't find it.
Thank you.

You may need to clean this up for your browsers, I tested in FireFox.
Working Example: https://jsfiddle.net/Twisty/ksd1gwp9/22/
JavaScript
$(function() {
function unlink(link) {
var txt = link.text();
var sp = $("<span>").html(txt);
link.replaceWith(sp);
}
function getObjectFromCursor() {
var obj, sel = window.getSelection() ? window.getSelection() : document.selection;
var range = sel.getRangeAt(0);
obj = $(range.startContainer.parentElement);
console.log(obj, range);
return obj;
}
$("#toolbar").on("click", "#toolbar_unlink", function() {
var target = getObjectFromCursor();
if (target.prop("nodeName") == "A") {
unlink(target);
}
})
});
The Range interface represents a fragment of a document that can contain nodes and parts of text nodes.
The range has nodes, for start and end of the selection, and these have references to parent elements. This is helpful for us to find the specific element the cursor is in or at. We can then target this element and replace it with plain text based on the content of the link.

Related

Reset content editable caret position

I am currently trying to create a syntax highlighter for Javascript and I currently facing the issue which I have found out is common with creating something like this which is setting the caret position to the end while the user types or edit contentEditable text.
I researched and found this and many other solutions here on SO but none works. It gets the position of the caret but never resets it so I am trying to find a workaround for this problem.
Below is the code I came up with.
html
<div id="editor" contentEditable="true" onkeyup="resetPosition(this)"></div>
<input type="text" onkeyup="resetPosition(this)" />
js
function getPos(e) {
// for contentedit field
if (e.isContentEditable) {
e.focus()
let _range = document.getSelection().getRangeAt(0)
let range = _range.cloneRange()
range.selectNodeContents(e)
range.setEnd(_range.endContainer, _range.endOffset)
return range.toString().length;
}
// for texterea/input element
return e.target.selectionStart
}
function setPos(pos, e) {
// for contentedit field
if (e.isContentEditable) {
e.focus()
document.getSelection().collapse(e, pos);
return
}
e.setSelectionRange(pos, pos)
}
function resetPosition(e) {
if(e.isContentEditable) {
let currentPosition = getPos(e);
e.innerHTML=e.innerHTML.replace(/[0-9]/g, "a");
setPos(currentPosition, e);
return;
}
e.value = e.value.replace(/[0-9]/g, "a");
setPos(currentPosition, e);
}
This works fine for text input but not for contentEditable divs.
When I type something like function, I get otincfun.
UPDATE: I was able to fix the setPos function by changing this line from document.getSelection().collapse(e, pos); to document.getSelection().collapse(e.firstChild, pos); but a new bug arose.
When I press ENTER Key, the caret goes back to the first line and first character. Please how do I fix?
Below is the fiddle link
https://jsfiddle.net/oketega/bfeh9nm5/35/
Thanks.
The Problems
document.getSelection().collapse(element, index) collapses the cursor to the child node that index points to, not the character index.
I was able to fix the setPos function by changing this line from document.getSelection().collapse(e, pos); to document.getSelection().collapse(e.firstChild, pos);
That will work if you are only replacing characters, but if you are creating a syntax highlighter, you will want to encase characters in span elements to style them. e.firstChild would then only set the position to an index within e's first child, excluding latter span's
Another thing to consider is that you may want to autocomplete the certain chars. The caret position before you manipulate the text may not be the same as after you do so.
The Solution
I recommend creating a <span id="caret-position"></span> element to track where the caret is.
It would work like this:
function textChanged(element) {
// 1
const text = setCursorMarker(element.innerText, element);
// 2
const html = manipulate(text);
element.innerHTML = html;
// 3
const index = findCursorIndex(element);
document.getSelection().collapse(element, index)
}
Every time the user types, you can get the current caret position and slip in the #caret-position element in there.
Overwrite the existing html with the syntax highlighted text
Find out where #caret-position is and put the caret there.
Note: The recommended way to listen for when the user types in the content-editable element is with the oninput listener, not onkeyup. It is possible to insert many characters by holding down a key.
Example
There is a working js fiddle here: https://jsfiddle.net/Vehmloewff/0j8hzevm/132/
Known Issue: After you hit Enter twice, it looses track of where the caret is supposed to be. I am not quite sure why it does that.

Count cursor position with tags included in range

I am trying to find position of cursor in contentEditable div which has HTML tags inside it.
Example of div would be:
<div id='editor'>
<h2>some</h2> text <span>goes here</span>
</div>
So if cursor is on letter "T" in 'text' word i want to get position that includes:
"<h2>some</h2> t"
not just
"some "
I have found solution that doesn't count tags in position: Get a range's start and end offset's relative to its parent container
This works fine but i need to count tags as well in final range.
Range object gives a range indicating DOM elements currently selected, it gives you information regarding in which element, at which position selection started and in which element, at which position selection ended.
function replaceSelectionWith(value) {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const {
startContainer,
startOffset,
endContainer,
endOffset
} = range;
if(startContainer === endContainer) {
const container = startContainer;
// nodeType === 3 means text node, can also check same using nodeName === '#text'
if (container.nodeType === 3) {
container.nodeValue = container.nodeValue.slice(0, startOffset) + value + container.nodeValue.slice(endOffset);
}
}
}
// Once you change the DOM, you can just get your HTML value from #editor.innerHTML
This is a simplest usecase, which is your scenario right now. But it is possible that startContainer and endContainer are different nodes or they can be DOM elements instead of being simple text nodes. This example is just to set you in the right direction.
You can refer to following documentation to understand how they actually work.
https://developer.mozilla.org/en-US/docs/Web/API/Selection
https://developer.mozilla.org/en-US/docs/Web/API/Range
EDIT: Working solution for the case asked in question
function insertTextAndGetHTML(value) {
replaceSelectionWith(value);
return document.getElementById('editor').innerHTML;
}

How to select text in tinymce editor, if I know it's position in html content?

The question is pretty simple, but I'm struggling to find any simple solution to make it work. I'm working with TinyMCE in the Wordpress.
Let's say that I have a position of some word in the html content, which I can get from editor like this:
tinymce.activeeditor.getContent();
So i need to select this word and move to selection (scroll to it).
For example, if content contains something like this:
...Bla bla bla some <strong>text</strong> and more text and banana and <img src=.../> more text bla blaa...
You can see the word "banana" out there, which, for example, I want to select. I know its position, let's say its start_pos = 30, end_pos = 35. It's not necessary a word, it can be a phrase (several words), but I know it's position, so there is no difference.
I need a way to select it in the Visual Editor (or Rich Editor), like I do for textarea (if Text mode is active) like this:
// cl_editor_lastfocus is textarea
cl_editor_lastfocus.setSelectionRange(start, end);
let charsPerRow = cl_editor_lastfocus.cols;
let selectionRow = (start - (start % charsPerRow)) / charsPerRow;
let lineHeight = cl_editor_lastfocus.clientHeight / cl_editor_lastfocus.rows;
// scroll !!
cl_editor_lastfocus.scrollTop = lineHeight * selectionRow;
cl_editor_lastfocus.focus();
Is it possible? As far as I can see, most answers here suggest to select textnode with this text and then set selection range. But I have no idea which textnode I have to select because my desired word can be anywhere in the document. Also, I can do that by wrapping the word with a span and then select this span node, but I don't want to keep that span afterwards, when selection will be changed or not needed anymore.
I guess, the easiest way to do selection in the Visual Mode is to wrap a string into span and select this span using editor.dom.select.
// We get editor's content, where 'editor' is a tinymce editor instance
var content = editor.getContent();
function selectText(start, end) {
// We have start and end position of the text which we want to select
// Then we wrap our word or frase between positions into span with unique id
let content_with_span = content.substring(0, start) + '<span id="'+start+'_'+end+'">' + content.substring(start,end) + '</span>' + content.substring(end, content.length);
// I keep the original 'content' variable outside, because in my case user can switch between various frases and when we switch to the next selection, we don't want to keep previos span in text
// Return content with span into editor
editor.setContent(content);
// Create selection
let selection = tinyMCE.activeEditor.dom.select('span[id="'+ start + '_' + end + '"]')[0];
// If selection exists, select node and move to it
if (selection) {
tinyMCE.activeEditor.selection.select(selection);
selection.scrollIntoView({behavior: "instant", block: "center", inline: "nearest"});
}
}
It works for me, and I hope it will help someone else.

Toggle text bold in the exact position from where selected in the browser in an editable iframe

I'm trying to make it so I can edit an iframe and be able to set selected text to bold. As you can see in the text below.
Now, I will explain how I am stuck, first of all here is some code:
var sel = $iFrame.get(0).contentWindow.getSelection();
if (sel.rangeCount) {
var text = sel.toString();
var range = sel.getRangeAt(0);
var parent = range.commonAncestorContainer;
if (parent.nodeType != 1) {
parent = parent.parentNode;
}
if (range.startOffset > 0) {
// change at offset
} else {
// change at beginning
}
}
So far I have acquired the parent node. But I'm struggling with how I can replace the content within the parent node with the new code including the tags. I also need it to consider that other text might already be bold and words might be repeated so i need it to make the exact highlighted option bold.
Also be able to reverse it to remove the bold from the selected text.
How can I proceed from this?
You don't want to insert tags yourself; just let the browser do it with the built-in commands. As long as the user has text selected in a contenteditable document, just running this function call will turn it bold:
document.execCommand('bold', false, null)
This works on practically every browser. Other commands and full documentation can be found here: https://developer.mozilla.org/en-US/docs/Web/API/document/execCommand

Javascript: add a text into text area

I would like to load a text into text area, when clicked in a map area.
When I click in the second area, I would like to add another (different) text
How can I make this happen?
http://jsfiddle.net/CQvKJ/
I don't know Mootools, so I did this in JS only without framework.
This may not be a good solution, but this is basically what you want to do, no matter how you append the text.
Sample
http://jsfiddle.net/CQvKJ/2/
Updated JS
function funzione1() {
// alert("add text : 1.");
var e = document.getElementById('my_text');
e.value += "1";
}
function funzione2() {
// alert("add text: 2");
var e = document.getElementById('my_text');
e.value += "2";
}
Identify the <textarea> by id.
Retrieve the element in the click handlers.
Set the element's value to the text you want to show up.
Forked fiddle.

Categories