I'm using Froala 2 and the documentation doesn't seem to have anything that implies a simple way to set the location of the caret, let alone at the beginning or end. I'm trying to seed the editor instance with a little content in certain cases and when I do using html.set, the caret just stays where it is at the beginning and I want to move it to the end. The internet doesn't seem to have anything helpful around this for v2.
Froala support provided an answer for me that works:
var editor = $('#edit').data('froala.editor');
editor.selection.setAtEnd(editor.$el.get(0));
editor.selection.restore();
As far as I know, Froala 2 doesn't provide any API to do this, but you can use native JavaScript Selection API.
This code should do the job:
// Selects the contenteditable element. You may have to change the selector.
var element = document.querySelector("#froala-editor .fr-element");
// Selects the last and the deepest child of the element.
while (element.lastChild) {
element = element.lastChild;
}
// Gets length of the element's content.
var textLength = element.textContent.length;
var range = document.createRange();
var selection = window.getSelection();
// Sets selection position to the end of the element.
range.setStart(element, textLength);
range.setEnd(element, textLength);
// Removes other selection ranges.
selection.removeAllRanges();
// Adds the range to the selection.
selection.addRange(range);
See also:
How to set caret(cursor) position in contenteditable element (div)?
Set caret position at a specific position in contenteditable div
Related
I can not come up with a suitable solution...
<p class="session" contenteditable="true">
Everything contained <b>**within**</b> this div is editable in browsers
that support <code>`HTML5`</code>. Go on, give it a try: click it and
start typing.
</p>
I want to add the appropriate HTML-tags while typing.
Of course it is no problem doing it by simple regex replacements. But I have problems setting the caret at the right place.
Say, in the example above, I want to emphasize the word "try". While typing the right * first and then the left star *, the text is converted to <i>*try*</i> but I want to preserve the position of the caret (set the the caret after the left *). And of cource vice versa, where the right * is set at last.
I find it more difficult to set the part contained <b>**within**</b> this say underlined. _contained <b>**within**</b> this_ becomes <u>_contained <b>**within**</b> this_</u>. So we are not in the same node.
I can think of complicated solutions, but since I am no expert in these text conversions techniques (range, window selection etc.) I wonder if there is a well known pratice of doing it.
Check out the Fiddle
I have tried to get the caret position as a first step and in the second step i have tried to put the caret back to the position .
$('.session').keyup(function(){
//alert("happening");
var sel = window.getSelection();
var offset=sel.anchorOffset;
// you can change the text in div by uncommeting the line below and replace your text with mathcing regex .
//$('.session').text($('.session').text().replace('/(\*.\*)+/','<i>$1<i>'));
setCaret(offset);
$('.session')
});
function setCaret(offset) {
var el = $('.session');
//alert();
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el[0].childNodes[0],offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
el.focus();
}
You need to figure out how to change the text in the div as per your needs. While replacing the text inside the div also maintain the formatting so that the solution works.
I'm currently building a Markdown editor for the web. Markdown tags are previewed in realtime by appending their HTML equivalents via the Range interface. Following code is used, which should be working according to MDN:
var range = document.createRange()
var selection = window.getSelection()
range.setStart(textNode, start)
range.setEnd(textNode, end + 2)
surroundingElement = document.createElement('strong')
range.surroundContents(surroundingElement)
var cursorRange = document.createRange()
cursorRange.setStartAfter(surroundingElement)
selection.removeAllRanges()
selection.addRange(cursorRange)
Firefox works: Some bold text
Chrome not: Some bold text
Any suggestions what could be wrong? Information on this subject are rare.
Answer
Thanks to #Tim Down, I fixed it using the invisible character workaround he describes in one of the links mentioned in his answer. This is the code I'm using now:
var range = document.createRange()
range.setStart(textNode, start)
range.setEnd(textNode, end + 2)
surroundingElement = document.createElement('strong')
range.surroundContents(surroundingElement)
var selection = window.getSelection()
var cursorRange = document.createRange()
var emptyElement = document.createTextNode('\u200B')
element[0].appendChild(emptyElement)
cursorRange.setStartAfter(emptyElement)
selection.removeAllRanges()
selection.addRange(cursorRange)
The problem is that WebKit has fixed ideas about where the caret (or a selection boundary) can go and is selecting an amended version of your range when you call the selection's addRange() method. I've written about this a few times on Stack Overflow; here are a couple of examples:
Set cursor after span element inside contenteditable div
How to set caret/cursor position in a contenteditable div between two divs.
I'm using SCEditor along jQuery in my website and I wonder how can I get the current caret position relative to the beginning of the textarea/div?
I tried things like:
$J('#textarea').sceditor('instance').getRangeHelper().selectedRange().startOffset
but that only gives me the position relative to the current DOM object, not the entire textarea.
What I'm trying to accomplish is to remove all the text after the caret from the textarea. Maybe there is another way to do that.
Thanks,
You can use rangeHelper().saveRange() to insert markers at the start and end of the selection and work from them.
e.g.:
var sceditor = $("textarea").sceditor("instance");
// Inserts spans with the ID #sceditor-end-start and #sceditor-end-marker
// at the start and end of the current selection
sceditor.getRangeHelper().saveRange();
// Get the DOM node for #sceditor-end-marker and remove all
// nextSiblings and parent nextSiblings from the editor
// which will remove everything after the end of the selection
var node = sceditor.getBody().find('#sceditor-end-marker').get(0);
while (node) {
while (node.nextSibling)
node.parentNode.removeChild(node.nextSibling);
node = node.parentNode;
}
// Restores the selection back to the positions of the
// #sceditor-end-start and #sceditor-end-marker markers
sceditor.getRangeHelper().restoreRange();
sceditor.focus();
Given the following HTML...
<p>Today is <span data-token="DateTime.DayOfWeek">$$DayOfWeek$$</span>,
</p>
<p>Tomorrow is the next day, etc, etc....</p>
Clicking on $$DayOfWeek$$ returns a DOM Range object (via a component, which is a WYSIWIG editor bundled with KendoUI).
I can then access the entire Element like so...
var element = range.startContainer.parentElement;
console.log(element);
which outputs...
<span data-token="DateTime.DayOfWeek">$$DayOfWeek$$</span>
What i am trying to figure out is how to construct a Range object that consists of the entire Element, as a Range.
The desired 'high level' behaviour is to single click a piece of text, and have the browser select all the text within that element, returning a Range object.
Happy to accept a jQuery solution.
HTML
<p>Today is <span data-token="DateTime.DayOfWeek">$$DayOfWeek$$</span>,</p>
<p>Tomorrow is the next day, etc, etc....</p>
JS
var span = document.querySelector('[data-token]');
span.addEventListener('click', function() {
var sel = window.getSelection();
var range = document.createRange();
sel.removeAllRanges();
range.setStart(span.childNodes[0], 0);
range.setEnd(span.childNodes[0], span.innerText.length);
sel.addRange(range);
});
Here's a fiddle for you:
http://jsfiddle.net/V66zH/2/
It' might not be super cross browser, but works in chrome. See JavaScript Set Window selection for some additional optimizations elsewhere.
Also assumes only one childNode as in your example html
Some additional reference for Ranges (https://developer.mozilla.org/en-US/docs/Web/API/range) and Selections (https://developer.mozilla.org/en-US/docs/Web/API/Selection)
here is a way i came up with that seems to work if i understand you correctly, that you want the element surrounding a click to produce a range containing everything in that element.
without the onclick code, which i assume you can handle, here is the DOM range code you describe:
var sel=document.getSelection(); //find the node that was clicked
var rng=sel.getRangeAt(); //get a range on that node
//now, extend the start and end range to the whole element:
rng.setStart(rng.startContainer.parentNode.firstChild);
rng.setEndAfter(rng.endContainer.parentNode.lastChild);
//DEMO: verify the correct range using a temp div/alert:
var t=document.createElement("div");
t.appendChild(rng.cloneContents());
alert(t.innerHTML);
I have a function that return an array (won't work in IE) with two elements
the html code of what the user select inside a div (id=text)
the range of the selection
In case the user select a simple string inside the text div the range return the correct values but when the user select a string inside an element child of div (div#text->p for example) range's values are related to the child element but i want them to be related to the parent (div#text)
Here there's a JsFiddle http://jsfiddle.net/paglia_s/XKjr5/: if you select a string of normal text or normal text + bolded text in the teatarea you'll get the right selection while if you select the bolded word ("am") you'll get the wrong one because the range is related to the child element.
There's a way to do so that the range is always related to div#text?
You could use my Rangy library and its new TextRange module, which provides methods of Range and selection to convert to and from character offsets within the visible text of a container element. For example:
var container = document.getElementById("text");
var sel = rangy.getSelection();
if (sel.rangeCount > 0) {
var range = sel.getRangeAt(0);
var rangeOffsets = range.toCharacterRange(container);
}
rangeOffsets has properties start and end relative to the visible text inside container. The visible text isn't necessarily the same as what jQuery's text() method returns, so you'll need to use Rangy's innerText() implementation. Example:
http://jsfiddle.net/timdown/KGMnq/5/
Alternatively, if you don't want to use Rangy, you could adapt functions I've posted on Stack Overflow before. However, these rely on DOM Range and Selection APIs so won't work on IE < 9.
If you don't want to use a library here is a way which worked for me.
The function returns the cursor offset relative to the textContent of the given node (not in relation to the sub nodes).
Note: The current cursor position must lie in the given node or in any of its sub-nodes.
It's not cross-browser compatible (specially not for IE), but I think it's not much work to fix that as well:
function getCursorPositionInTextOf(element) {
var range = document.createRange(),
curRange = window.getSelection().getRangeAt(0);
range.setStart(element, 0);
range.setEnd(curRange.startContainer, curRange.startOffset);
//Measure the length of the text from the start of the given element to the start of the current range (position of the cursor)
return document.createElement("div").appendChild(range.cloneContents()).textContent.length;
}