I'm working on a way to create annotations in HTML text using JavaScript. The way it works, the user clicks on a contenteditable <div> and I get the position of the cursor based on where they click. Then, when I'm placing the annotation in the text, I go to that character position to insert a footnote indicator. The problem is that the position of the cursor in the contenteditable <div> doesn't take HTML tags into account. So for example, if the <div> contained the following:
AB<b>CD</b>EF
And I placed the cursor between "C" and "D", the position is 3 because it ignores the <b>. Is there a way to get the cursor position including HTML tags so that it is consistent when I go back and place my markers? The <div> doesn't necessarily need to be contenteditable if that opens up other solutions.
(I'm currently using this solution to get the position: Get a range's start and end offset's relative to its parent container)
I think it's because <b> is no container and <div> is a container.
I didn't try the code but try to assign "display: inline-block" to <b> so it gets recognised as a container. Maybe it works.
Related
I have a function that separates words into separate span tags and updates the div I am typing into but every time I update the innerHTML the cursor moves to the start of the box so the next character I type outputs behind the last one instead of after it. Could someone tell me how I can stop the cursor from doing this?
Here is my code
const editorDiv = document.getElementById('editor');
function wrapWords(str, tmpl) { //separates words into <span>
return str.replace(/\w+/g, tmpl || "<span>$&</span>");
}
editorDiv.addEventListener('keydown', function(event) {
editorDiv.innerHTML = wrapWords(editorDiv.innerText)
});
<div id="editor" contenteditable="true">
<span>hello</span>
</div>
When I type into the div text looks like this:
.siht ekil skool txeT
One way I have solved this in the past is to use 2 seperate elements, positioned on top of each other, with position: absolute, so that they overlap in a pixel-perfect manner.
The element on top is a regular input element, and its text color is set to transparent.
The element behind it contains the styled text (spans etc.)
This way, you can replace the styled content as the user types, without interfering with the input element's cursor at all.
Sorry that I don't have an 'essential code snippet' at hand, but I hope you get the idea. It did work well in practice for me.
I'm building an Editor that does syntax highlighting on code. My current approach is to just take the text in the editor (a div with contenteditable set to true) and check for regex matches in the string. I then replace the matches with <span> elements and apply some styles to those span elements. Then I replace the complete text in the divider with the new text using the .innerHTML attribute. This works just fine, but I have to type backwards, because upon inserting the text, my cursor is reset to position zero. I tried recording the value of selectionStart before inserting and then doing
element.selectionStart = oldSelectionStart + 1;
but it didn't work. I assume this is because of chrome's render pipeline, where JavaScript is run before rendering the page, and the cursor is reset upon render, not upon set... Can anyone help? How do I manage to keep the cursor where it was?
element.selectionStart works for input elements, not for contentEditable elements. You should try creating a Range object, set its startContainer & startOffset and collapse it to set caret at the required position. Refer to [https://developer.mozilla.org/en-US/docs/Web/API/Range] for details.
PS: If you would like to set your cursor to the 'end of line', you could do it easily with range.selectNode(requiredNode) and range.collapse(true) to move the caret to the end of node, i.e., line
I'm trying to hide the blinking caret when clicking on certain elements in wysiwyg editors (in my case redactor.js) but I suppose all are the same.
I don't want to disable the whole input area, it just hide the caret and not allow any input when clicking certain elements.
You can set caret position when clicking certain elements.
Set cursor position to a specific position of an element :
$('#redactor').redactor('setCaret', element, 4);
Set cursor to the end of an element :
$('#redactor').redactor('selectionEnd', element);
You can set contenteditable=false attribute on elements that should not be editable.
<div contenteditable="true">
editable part
<span contenteditable="false">read only part</span>
editable part
</div>
See a live example: http://jsfiddle.net/Ebhxy/
However, as everything related to contenteditable - if editor which you use does not have support for nested non-editable elements,the UX will be poor and very buggy.
Say I have the following text:
this is some example c|ode
The pipe symbol is where the cursor is in a nicEditor div. nicEditor has a function to get the position of the cursor "getRng()".
However getRng() doesn't get the position of the cursor including HTML, and as far as I can see no such function exists (correct me if I'm wrong).
The code in the nicEditor div might actually be:
<span style='font-weig|ht: bold;'>this i</span>s some example code
You can see my problem. I'm trying to insert something into the nicEditor div and it gets inserted into the middle of a HTML tag.
My question is this: Is there a way to convert the position of the cursor with the HTML tags included? Or is there any other alternative solution?
I worked it out in the end, it wasn't an easy solution. I had to write a state machine which has four states "Text"=>"HTML"=>"String"=>"Character". Basically I wrote jQuery plugin which takes the string and the position and shifts accordingly.
For those who haven't worked with the Google Docs editor here's a short explanation of how it works:
Google Docs has no visible editable textarea or contentEditable elements.
Google Docs listens for keydown/press/up in a separate iFrame where they place the OS cursor for event listening.
When the iFrame catches an event Google handles it by performing the equivalent operations on the visible document.
The "caret" in Google Docs is a DIV that is styled and scripted to look and act like an OS cursor.
With that out of the way, here's my request:
I'm working on a plugin that interacts with the Google Doc and I need to be able to do two things:
Highlight words with an opaque overlay DIV.
Determine cursor position inside a word.
I've been exhausting a lot of ideas about just how to handle this, but so far I've only manage to get a buggy solution for the latter problem (I perform a backspace, determine where the text changed and undo the backspace).
I'm looking for all the best ideas you can come up with to solve these problems. They don't need to be cross browser, but they do need to be able to be turned into something robust that will also handle things such as font size changed mid line.
A little bit of extra info explaining what a Google Doc looks like in HTML:
<wrapper> // Simplified wrapper containing margins, pagination and similar
<div class="kix-paragraphrenderer"> // single DIV per page wrapping all content
// Multiple paragraphs separated by linebreak created by Enter key:
<div class="kix-paragraphrendeder">...</div>
<div class="kix-paragraphrendeder">...</div>
<div class="kix-paragraphrendeder">
// Multiple wrapper divs created by Google's word wrapping:
<div class="kix-lineview">...</div>
<div class="kix-lineview">...</div>
<div class="kix-lineview">
// Single inner wrapper, still full width of first wrapper paragraph:
<div class="kix-lineview-content">
// Single wrapper SPAN containing full text of the line, but not display:block
<span class="kix-lineview-text-block">
// Multiple spans, one per new font change such as normal/bold text,
// change in font size, indentation and similar:
<span>This is normal text</span>
<span style="font-size:40px; padding-left:4px;">This larger text.</span>
<span style="font-weight:bold; padding-left:10px;">This is bold text</span>
<span style="padding-left:4px;">More normal text</span>
</span>
</div>
</div>
</div>
</div>
</wrapper>
After more tinkering I came to the conclusion that it is extremely troublesome - if not impossible - to try and programmatically determine cursor position with regard to a letter inside a <span>, simply because the <span> is the smallest element that is measurable (correct me if I am wrong).
So how to solve the problem? Here's what I ended up doing:
I create an offscreen positioned <div>
I get the text of the current paragraph (<div class="kix-paragraphrenderer">) - I could get the entire text, but wanted to limit the computational load.
I extract each single character of the paragraph by looping through its children in the following way:
Loop through linveviews of the paragraph (<div class="kix-lineview">)
Get the lineview content (<div class="kix-lineview-content">)
Loop through text blocks of the lineview content (<span class="kix-lineview-text-block">)
Loop through <span>'s of the text block
Loop through innerText of the <span>
I append each character in my offscreen <div> with the currently applied style extracted from style.cssText of the current <span>
For each character appended I measure the width of the <div> and save this in an array. I now have a position of each single character.
I measure the position of the cursor relative to my widths and voila - I know where the cursor is positioned in the text.
This is obviously a bit simplied (I left out details about margins and paddings of the different elements), but it covers the idea behind how it's possible to get the cursor position.
It works quite well, but there are many pitfalls and a lot of measuring required. On top of that it's also required to post-parse the text if you want to use it for anything, since tabs, spaces and linebreaks aren't always included in innerText (depending on where these are in the text, Google may or may not make them through positioning of new elements).
I made something like Kix two years ago Google Docs. And for any HTML design and yes, for IE6 too :-) How? All we need is to compute letter absolute position. How? Replace textNode with inline element without layout, that's important, and then use Element.getClientRects I remember I also needed wrap just letter and compute its position via fast and reliable https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect
The trick how to detect lines and wraps for home and end keys was based on some vertical heuristic letter position change. Something like if base line is different, than stop caret walking. It was pretty fast and with any markup and without any caching. Holy grail :)
The only not resolvable problem was justified text, because letters were distributed randomly and spaces between them was not computable.
That project is dead http://webeena.com now. Bad management killed it (and me almost too).