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.
Related
I have a div which gets populated dynamically with some text that may or may not contain a link(s).
For eg:
<div>Welcome to Stackoverflow.
Someone will provide an answer to this question.
Just sit back and relax, or go check your
mail
</div>
When I truncate it using solutions provided here, the output I get is:
Welcome to Stackoverflow. Someone will
provide an answer to this... mail
The reason this is happening is that the anchor (a) tag is an inline element while the "ellipsis" property works only on block elements. Explained here.
I can assign the last-child A tag display:block, but if it's not part of the truncated text, then the text following it breaks to a new line.
Interestingly, if I simply add a "." (period) or even a non-breaking space at the end after the link, then the truncation occurs normally and the link does not jump in after the ellipsis.
Is there a cleaner approach?
Just to add: I'm looking for a solution that need not be cross-browser, but works for Chrome, since I'm coding for CEF.
Assign "display:inline-block" to the last-child A, instead of "display:block" ;-)
UPDATE:
I read js solution proposed in thread you linked and I updated that jsFiddle into this one. Is your desired behaviour?
HTML:
<div style="width:200px; height:80px; border:1px solid red">Welcome to Stackoverflow.
Someone will provide an answer to this question.
Just sit back and relax, or go check your
mail
</div>
JS:
$(document).ready(function(){
$("div").ellipsis();
});
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.
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
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.
I have website that converts Japanese Kanji into Romaji (roman letters):
and the output shows and hides with CSS what the user needs to see depending on their input criteria. For example:
<div id="output"><span class="roman">watashi</span> <span class="english">I</span></div>
The interface allows the user to flip between and output of watashi or I depending on what they want to see. The CSS hides one or the other using jQuery and a toggle button. (the hiding mechanism involves simple adding a class to the body and letting CSS do its thing).
The problem is that when users copy/paste the text into Word it copies everything. So I decided to use a system to copy paste the text using JavaScript and jQuery, but the problem repeats itself:
$('#output').text() outputs watashi I even if I is invisible on the page itself rather than watashi. Is there any way to get just the visible text?
the other solutions did not give me what I needed.
Short Answer
my answer is :
$('#output *:not(:has(*)):visible').text()
plunkr
TL;DR
The problem with marcgg's solution
You should not ask the text of all element under some root element..
why? - it will repeat output and ignore hidden flag
lets look at a simple example
<div id="output" class="my-root">
<div class="some-div">
<span class="first" style="display:none"> hidden text </span>
<span class="second" > visible text </span>
</div>
<div>
now if I do $('#output').children(":visible").text()
I will get .some-div and .second..
when in fact .some-div is of no concern to me..
when I ask for text() on those elements, .some-div will return the hidden text as well..
so technically marcgg's solution is wrong IMHO...
The reason for my answer
Now, in order to properly answer the question, we have to make an assumption. One that, for me, seems reasonable enough.
The assumption is that text only appears in leaf elements..
So we won't see something like this:
<div id="output" class="my-root">
<div class="some-div">
<span class="first" style="display:none"> hidden text </span>
<span class="second" > visible text </span>
</div>
some text here..
<div>
Why does this assumption seem reasonable to me? two reasons:
Because it is hard to maintain a page that is constructed this way - and with time people with experience learn that and avoid it.
It is easy to convert your html to such a structure. just wrap parents' text with spans. So even if this assumption does not exist right now, it is easy to get there.
With that assumption, what you want to do is request all leaf elements (elements without children) , filter out the visible, and ask for their text..
$('#output *:not(:has(*)):visible').text()
This should generate the correct result.
Gotta have text outside leaf element?
the comments suggest sometimes you just got to have text outside leaf element
<div> This is some <strong style="display:none"> text </strong> </div>
As you can see, you have <strong> as a leaf and it is common to have text outside it like in this example.
You could go around it with the workaround I suggest above.. but what if you can't?
You can clone the dom and then remove all hidden elements.
The problem here is that in order for :visible selector or :hidden selectors to work, I must have the dom element on the document (which means actually visible to the user).
And so, this method comes with some side effects, so be careful.
Here is an example
for this html
<div id="output" class="my-root">
<span>
some text <strong style="display:none">here.. </strong>
</span>
</div>
This javascript works
$(function(){
var outputClone = $('#output').clone();
$('#output :hidden').remove();
console.log($('#output').text()); // only visible text
$('#output').replaceWith(outputClone);
console.log($('#output').text()); // show original state achieved.
})
see plunker here
as mentioned - side effects may appear like a momentary flicker, or some initialization script that should run.. some may be avoided with some original thinking (div with size 1px/1px to contain the clone alongside original content?) depending on your scenario.
Use the :visible selector of jQuery
In your case I think you want to do:
$('#output').children(":visible").text()
Try this in modern browsers (here 'element' is a non-JQuery DOM object):
function getVisibleText(element) {
window.getSelection().removeAllRanges();
let range = document.createRange();
range.selectNode(element);
window.getSelection().addRange(range);
let visibleText = window.getSelection().toString().trim();
window.getSelection().removeAllRanges();
return visibleText;
}
then:
getVisibleText(document.getElementById('output'));
Guy has the correct answer.
However, I was dealing with a "this" object, so to get his answer to work you need to use the following syntax...
$('*:not(:has(*)):visible', this).text()
var lookup = function(element, text) {
//DFS Recursive way of finding text on each level
//Visible only works on elements that take up space(i.e. not fixed position elements)
var results = element.children(':visible');
//Look at the text at each level with the children removed
var newText = '';
results.each(function(index, value) {
newText += $(value).clone()
.children()
.remove()
.end()
.text();
});
var moreResultText = '';
results.each(function(index, value) {
moreResultText += lookup($(value), text);
})
if (results.length > 0) {
return text + newText + moreResultText;
} else {
return text;
}
};
lookup($('#output'), ''));
Most of the other functions fall apart when run on large sections of a page, this should be a more accurate way to determine what is actually displayed to the user, without corrupting the page, and without returning text that is not visible to the user.
Be careful of course, this does not preserve any sense of formatting, and the spacing of the output may not be correct between elements. Also, it probably does not correctly order the returned text, in these aspects its usages will be limited. Another consideration is the real definition of visible is a little hard to nail down, but for this example I accept that ":visible" works for most common cases.
I use it to check if a page contains visible text(just run it on the body element), but it would probably work for this example too.
Instead of hiding a span, remove the span element and keep a reference to it. When the user clicks on the toggle button, remove the other one and insert the one you kept a reference to. The user won't be able to select something that isn't in the DOM anymore.