Locking elements in contenteditable="true" div - javascript

I have a contenteditable div that's used for user input, when a button is clicked it shows options to replace certain words. First it strips away all html and creates span elements where a word can be replaced. The mark up of these words is different and I face some problems.
When clicking directly before or after a span and typing text the text will have the same markup as the span. It's very hard to add words on a line that only has the span. I was thinking of solving this by padding the spans with but it looks kind of strange.
User can click in the span and change it, I would rather have the user click on the span and choose a replace or ignore option before changing it. In other words it needs to be locked. I was thinking of doing this by capturig keyup and if it comes from a span then e.preventDefault() on it but it's a bit of a pain to program it.
So my questions are:
Is there an easy way of locking a span element in a contenteditable that doesn't involve capturing key up and preventDefault (have not tried yet and not even sure if it'll work)?
When clicking or moving the cursor directly next to a span and typing text it'll be part of the span, is there a way to not have it be part of the span? Padding the span with might work but it looks strange when the span is the first word on the line. To illustrate this I've posted an example html file below:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<title>Example</title>
</head>
<body>
<div contenteditable="true"></div>
<script type="text/javascript">
document.getElementsByTagName("div")[0]
.innerHTML="<span style='color:green'>hello</span>"
</script>
</body>
</html>

Mark the span inside your editable container contenteditable="false". It'll solve both problems at the same time.

As for locking elements in the contenteditable div; I am using spans that need to be locked and they do not trigger keydown events so have set the event on the div and check through range if it's a locked element.
I am using google closure library so getting the range is simple:
goog.events.listen(document.getElementById("userInput"),//userInput= contenteditable div
[goog.events.EventType.KEYDOWN,
goog.events.EventType.PASTE],
function(e){
if(32<e.keyCode&&e.keyCode<41){//page up,down,home,end and arrow
return;
}
var el=goog.dom.Range.createFromWindow()
.getContainerElement();
if(el.className.indexOf("locked")!==-1){
e.preventDefault();
}
});
I'll look into padding the locked span with if no whitespace exist before or after the element and post it here once I get the solution.
Ended up padding the spans without checking since the extra whitespace will be removed later anyway. Looks kind of funny but it's better then not being able to add text.

Related

Focus on next paragraph in contenteditable div / Summernote

I am using Summernote but replicating the Medium editor.
When inserting images, I only allow them to be inserted into their own paragraph tag. No text can be mixed in side paragraph tags with an image.
I give each paragraph with an image inside a class of 'has-image'. What I now want to do it not allow the user to enter any text inside of the paragraph if it has that class.
If they try to click inside the tag it will instead focus to the next paragraph.
Any help how to do this? I have tried triggering a click on the next paragraph but no luck:
$(document).on('click', '.has-image', function() {
$(this).next('p').click();
});
I can set the text of the next paragraph so I know its selecting fine but cant think of a way to actually place the cursor inside.
JSFiddle as example: http://jsfiddle.net/vXnCM/5583/
U may need to work with Range
$(document).on('click', '.has-image', function() {
r = document.createRange()
r.setStart($(this).next('p')[0],0);
window.getSelection().removeAllRanges();
window.getSelection().addRange(r);
});
NOTE: It only works for most modern browsers except IE.
For IE capability, Check https://code.google.com/archive/p/ierange/

Placing the caret outside of a dynamically produced span

So I have perused the piles of information on SO on this subject and finally resorted to asking.
I'm working with a contenteditable div and need to mimic twitter's on-the-fly conversion from unstyled text to styled text for hashtags and mentions (please do not link me to twitter-text, that is not, in and of itself, the answer, I've tried).
The obvious solution here would be to enclose the text that needs to be styled in a span or, more generally, something I may attach styles to.
I can do that, but the problem is that the caret then does weird stuff like jump to the beginning.
I've found a solution (Tim Down's) which works nicely, but it leaves the caret in the span, so the ensuing text is still styled, which is not what I want.
He suggests elsewhere that this is because webkit browsers are opinionated and will force the caret in the span, instead of placing it outside of the span. There are two proposed hacks, first, he suggests that you enclose the text in a blocks instead of spans. Which I've tried, but doesn't work. He then suggests that I create a zero width white-space char, and then select that.
So how do I do that? Or are there any other alternatives?
(working code welcome)
Edit 1:
Reference to "cursor" was corrected to "caret"
Can you please try placing the content in the label instead of span ?
That will force the caret position to be at the end.
Please check with the following code....
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script language="javascript">
function addSomeText()
{
var dynaspan = "<label>"+new Date().getTime()+"</label>"
document.getElementById("clickable").innerHTML += dynaspan;
}
</script>
</head>
<body>
<div style="width:auto;height:240px;border:1px solid red;" contenteditable="true" id="clickable">
</div>
<button onclick="javascript:addSomeText();">Say Something!</button></body>
</html>
Click on Say Something
Hope it helps!
This may be a duplicate of this question. Dutzi, in that question, recommended adding display: inline-block and a <br> tag.
If you've seen that, or that doesn't work, you can add a null-byte-like character to a string with var string += '\0'; or var string += '\u00000'; and select that null byte with
input.setSelectionRange(input.value.length-1, 1);
input.focus();
If this is not what you mean (I wasn't 100% clear on what exactly was happening without code), there may be other solutions, but at the moment, that's how to add and select a zero-length character in javascript.

Link tag <a> able to select text

I need to change the text that appears in the status bar of Chrome. Since it's not possible to use status.text anymore (for security reasons) I am wrapping an <a> tag over <body> so my code is <body>...</body> .
It's working as expected cause every time the user moves the mouse inside the page the status bar shows exactly what is inside the href attr of <a>. The problem: I did not realize till now that I cannot select any text inside the page cause if it's treated as link. I am using return false onclick event of and it works great cause the user is never redirected however it's not possile to select text inside the <a>.
Is there a CSS property that allows me to change that behaviour? It only occurs if <a> tag.
For the sake of hacking. This is invalid markup and bad code, but as a proof of concept (at least for Chrome).
One could use various combinations of mouse events, range selection and editable. Tricky part is to calculate what and where to select.
Sample code should give you selection of the first words in a paragraph; as in: click on the start of each paragraph like somewhere in "Lorem ipsum" or "Duis posuere" to select some of the words. This could then be combined with mousedown, mousemove, mosueup etc. to select correct text.
Chrome only Fiddle
You can try the CSS property pointer-events.
a{pointer-events:none} would disable the mouse event for that element.
But a better approach would be to add the URL in data-attribute and on click event you can navigate to those URL with location.href.
Will that help?
Ok, just for fun, and this is not the real hack but it something close:
Do like this:
<body>
<div class=container>bla bla bla</div>
<a href="my custom text" id=the_hack></a>
</body>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<script>
$(".container").hover(function(e){
$("#the_hack").trigger("focus");
});
</script>
Of course it will have some limitations, when you select text the status bar will disappear, also if you highlight text move the mouse out of the div than back in, the selection will be lost, but hey it's a hack.
See here what I mean.

How to restore the selection / caret position when the html structure of an editable div element is changed?

I wrote a simple test to change the text in an editable div's content. The html structure is changed but the text is the same.
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Hello</title>
<script type="text/javascript" src="rangy-core.js"></script>
<script type="text/javascript" src="rangy-
selectionsaverestore.js"></script>
</head>
<body>
<div id="show" class="code" contenteditable="true"><span
style="color:red">12345</span>12345</div>
<script type="text/javascript">
window.setTimeout(function () {
// save selection / caret position
var show = document.getElementById("show");
show.innerHTML = "1234512345";
// restore select / caret position
}, 5000)
</script>
</body>
</html>
I've tried rangy like this:
var s = rangy.saveSelection();
var show = document.getElementById("show");
show.innerHTML = "1234512345";
rangy.restoreSelection(s);
But it report an error:
Rangy warning: Module SaveRestore: Marker element has been removed. Cannot restore selection.
Does rangy support the feature I mentioned above? If yes, how should I use it? If no, what should I do to implement that?
UPDATE: In my scenario I have to replace all the innerHTML since the text would be formatted into a very rich style. But in my case the text is always the same without the styles. Is that any possible way to record the selection and caret position and set it back? What kind of API I should use?
The error message is pretty descriptive of the problem, which is that Rangy's save/restore module uses hidden elements with specific IDs to mark the start and end of the selection range, meaning that if these elements are removed, the whole thing falls down.
The obvious solution is to store the selection as character positions within the visible text. This isn't as easy as it may appear, however. I'm actively working on a module to do this properly and hope to release something in the next couple of months. In the meantime, here's a naive solution that is good enough for many situations:
replace innerHTML in contenteditable div

Allow Tabbing in a contentEditable div with execCommand

I am making an application where I need for a contentEditable div to be allowed to have tabs in it. I have already figured out that it is really not possible to have to work correctly. So is there a way that on keyDown it adds the HTML code for a tab, which is
What I have so far is this
document.getElementById('codeline').contentEditable='true';
document.getElementById('codeline').onkeydown=function(e){
if(e.keyCode==9){
e.preventDefault();
//document.getElementById('codeline').contentWindow.document.execCommand("InsertHTML",false," ");
//Thought this would work but it doesn't
}
}
If anybody knows if there is a way to do this, or if that is the way and I am simply doing it wrong, please tell me! Thanks!
The HTML spec specifies that a TAB char should be treated as a single white space except for when it's contained inside a <pre> element.
The code that you posted above does work and it does insert a TAB char but it's just displayed by the browser as a white space. If you can put your entire editable content in a <pre> tag then your tabs will show up.
If you only want to use the tabs to indent content, you can also look into
execCommand('indent', false, null)
For future readers, perhaps a simpler solution is to just use the 'pre' white-space style. original post here.
white-space: pre;
"indent" command, firefox wraps the selected text with blockquote element.
So one can define a margin-left or padding-left property for blockquote element to represent tabbing.
The "outdent" command will remove the blockquote element and hence the styles.

Categories