Placeholder in contenteditable - focus event issue
Asked Answered
B

11

68

I have been trying to ask this before, without any luck of explaining/proving a working example where the bug happens. So here is another try:

I’m trying to replicate a placeholder effect on a contenteditable DIV. The core concept is simple:

<div contenteditable><em>Edit me</em></div>

<script>
$('div').focus(function() {
    $(this).empty();
});
</script>

This can sometomes work, but if the placeholder contains HTML, or if there some other processing being made, the editable DIV’s text caret is being removed, and the user must re-click the editable DIV to be able to start typing (even if it’s still in focus):

Example: http://jsfiddle.net/hHLXr/6/

I can’t use a focus trigger in the handler, since it will create an event loop. So I need a way to re-set the caret cursor in the editable DIV, or in some other way re-focus.

Bergschrund answered 1/2, 2012 at 9:25 Comment(0)
U
25

You may need to manually update the selection. In IE, the focus event is too late, so I would suggest using the activate event instead. Here's some code that does the job in all major browsers, including IE <= 8 (which a CSS-only alternative will not):

Live demo: http://jsfiddle.net/hHLXr/12/

Code:

$('div').on('activate', function() {
    $(this).empty();
    var range, sel;
    if ( (sel = document.selection) && document.body.createTextRange) {
        range = document.body.createTextRange();
        range.moveToElementText(this);
        range.select();
    }
});

$('div').focus(function() {
    if (this.hasChildNodes() && document.createRange && window.getSelection) {
        $(this).empty();
        var range = document.createRange();
        range.selectNodeContents(this);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
});
Underthecounter answered 1/2, 2012 at 10:16 Comment(5)
Another +1 for the RangeMaster. I thought by keeping it simple I'd be avoiding the cross browser issues, but that was rather naive of me.Surculose
Sweet! I was hoping you’d be around mr. RangeMaster!Bergschrund
@AndyE: Thanks. There may be a simpler way. There ought to be, really, but I couldn't find one quickly.Underthecounter
I’ve been banging my head the last two days on this one, trying all kinds of quirks. I was even adding an absolute positioned placeholder at the back of my head, so this solution is simple enough for me! I agree though, there should be a simpler solution...Bergschrund
Awesome! You saved me a lot of headaches :)Yt
C
168

Here is a CSS only solution augmenting some of the other answers:-

<div contentEditable=true data-ph="My Placeholder String"></div>
<style>
    [contentEditable=true]:empty:not(:focus)::before{
        content:attr(data-ph)
    }
</style>

EDIT: Here is my snippet on codepen -> http://codepen.io/mrmoje/pen/lkLez

EDIT2: Be advised, this method doesn't work 100% for multi-line applications due to residual <br> elements being present in the div after performing a select-all-cut or select-all-delete on all lines. Credits:- @vsync
Backspace seems to work fine (at least on webkit/blink)

Cartesian answered 21/8, 2013 at 22:24 Comment(14)
This would be exceptionally cool, except... can't get it to work. Can you post a working jsfiddle?Misspeak
@Misspeak Check the my edit. Hope you dont mind my using codepen. Jsfiddle acted up on me severaly (and i find codepen having better features)Cartesian
This should be the accepted answer. It's a little more complicated than the one from @amwinter but it keeps the placeholder in the html instead of the css.Misspeak
True! Another advantage of the complexity tradeoff is that if you have multiple contentEditable elements, all you have to do is specify a data-ph placeholder for each and the same CSS code takes care of all of them.Cartesian
@Misspeak I agree; this is clearer to a reader of the html and easier to templatize.Wholesale
This looks good but what's browser support like? Doesn't seem quite right in IE 9 and 10 (placeholder only disappears when typing starts) and doesn't show anything in IE <= 8.Underthecounter
With IE <= 8, HTML5 is alien speak.....thus the data-ph attribute gets ignored. You can fix that by swapping it with the title attribute. As for IE>8, i doubt there's much we can do...but IMHO the behavior you describe doesn't vary much from the 'real deal'Cartesian
In Chrome the focus goes weird with this trick. I click to change the value and start typing and nothing shows up. It also broke my angular code somehow. I don't understand the effects of using css content, but I'd be wary.Wayland
~Jason McCarrell use 'display: inline-block' instead of 'display: inline' and it should work :)Annabal
@eugene you're right. I had incorrectly assumed :not and :empty belonged to CSS2 (my bad). However, content:attr(title) works with ie8 if you specify a <!DOCTYPE>. So you can (in theory) modify the example to handle :empty with javascript. Holla if you need an example. [CC @TimDown] `Cartesian
This is NOT the answer, this fails and is unreliable. Try writing 2 lines text, then select all and delete. there will still be some junk HTML inside the contentEditable, which will prevent the :empty from being triggered. fail.Octodecimo
You're right @vsync! That's a very good find. Thank you for sharing.Cartesian
@Misspeak make sure there are no accidental spaces in your div tag. I had the problem where my generated code looked like <div> </div> instead of <div></div>Lame
elegant - good solution. All you need is color:#a9a9a9; to further give that placeholder "feel".Achaean
E
29

I've just published a plugin for this.

It uses a combination of CSS3 and JavaScript to show the placeholder without adding to the content of the div:

HTML:

<div contenteditable='true' data-placeholder='Enter some text'></div>

CSS:

div[data-placeholder]:not(:focus):not([data-div-placeholder-content]):before {
    content: attr(data-placeholder);
    float: left;
    margin-left: 5px;
    color: gray;
}

JS:

(function ($) {
    $('div[data-placeholder]').on('keydown keypress input', function() {
        if (this.textContent) {
            this.dataset.divPlaceholderContent = 'true';
        }
        else {
            delete(this.dataset.divPlaceholderContent);
        }
    });
})(jQuery);

And that's it.

Embrocation answered 25/1, 2013 at 22:9 Comment(2)
NB: Since I wrote this answer, I've updated the plugin for IE compatibility and non-div elements. See the linked GitHub repo for the latest version.Embrocation
Does it work if I am setting div text from backend? In my case placeholder and my text are concatedPotemkin
U
25

You may need to manually update the selection. In IE, the focus event is too late, so I would suggest using the activate event instead. Here's some code that does the job in all major browsers, including IE <= 8 (which a CSS-only alternative will not):

Live demo: http://jsfiddle.net/hHLXr/12/

Code:

$('div').on('activate', function() {
    $(this).empty();
    var range, sel;
    if ( (sel = document.selection) && document.body.createTextRange) {
        range = document.body.createTextRange();
        range.moveToElementText(this);
        range.select();
    }
});

$('div').focus(function() {
    if (this.hasChildNodes() && document.createRange && window.getSelection) {
        $(this).empty();
        var range = document.createRange();
        range.selectNodeContents(this);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
});
Underthecounter answered 1/2, 2012 at 10:16 Comment(5)
Another +1 for the RangeMaster. I thought by keeping it simple I'd be avoiding the cross browser issues, but that was rather naive of me.Surculose
Sweet! I was hoping you’d be around mr. RangeMaster!Bergschrund
@AndyE: Thanks. There may be a simpler way. There ought to be, really, but I couldn't find one quickly.Underthecounter
I’ve been banging my head the last two days on this one, trying all kinds of quirks. I was even adding an absolute positioned placeholder at the back of my head, so this solution is simple enough for me! I agree though, there should be a simpler solution...Bergschrund
Awesome! You saved me a lot of headaches :)Yt
W
24

just use css pseudo-classes.

span.spanclass:empty:before {content:"placeholder";}
Wholesale answered 10/1, 2013 at 17:1 Comment(2)
This was great. Then you can do span.spanclass:active:before{content:""} to remove it when selectedAloin
@BobtheMagicMoose, thank you! Seems like an excellent solution to solving a very annoying problem.Moua
M
14

I found that the best way to do this is to use the placeholder attribute like usual and add a few lines of CSS.

HTML

<div contenteditable placeholder="I am a placeholder"></div>

CSS

[contenteditable][placeholder]:empty:before {
    content: attr(placeholder);
    color: #bababa;
}

Note: the CSS :empty selector only works if there is literally nothing in-between the opening and closing tag. This includes new lines, tabs, empty space, etc.

Codepen

Mating answered 8/12, 2013 at 12:38 Comment(1)
This solution has an issue in FF. on near right side border if you click twice, cursor hangs there.Gypsophila
C
11

All you need is this little solution

[contenteditable=true]:empty:before{
  content: attr(placeholder);
  display: block; /* For Firefox */
}

Demo: http://codepen.io/flesler/pen/AEIFc

Chink answered 12/12, 2015 at 16:42 Comment(4)
Works. Tested in firefox, chrome, and Safari.Burgomaster
Eye, but not Edge (cursor appears at the end)Garrett
As of August 2020 this works in Edge (macOS version anyway).Circumcision
Encountered a problem with this method in Chrome where you have to click twice to place the caret, if you click right on the placeholder. Got it fixed by adding pointer-events: none; (found in your codepen css). Mentioning here for those who are looking for a solution for that particular Chrome issue.Guess
P
4

Here's my way:

It uses a combination of jQuery and CSS3. Works exactly like the html5 placeholder attribute!.

  • Hides itself right away when you input the first letter
  • Shows itself again when you delete what you input into it

HTML:

<div class="placeholder" contenteditable="true"></div>

CSS3:

.placeholder:after {
    content: "Your placeholder"; /* this is where you assign the place holder */
    position: absolute;
    top: 10px;
    color: #a9a9a9;
}

jQuery:

$('.placeholder').on('input', function(){
    if ($(this).text().length > 0) {
        $(this).removeClass('placeholder');
    } else {
        $(this).addClass('placeholder');
    }
});

DEMO: http://jsfiddle.net/Tomer123/D78X7/

Petiolate answered 24/8, 2013 at 6:41 Comment(1)
The input event is pretty new in contenteditable elements. It isn't supported at all in IE, for example.Underthecounter
B
1

Here's the fix that I used.

<div contenteditable><em>Edit me</em></div>
<script>
$('div').focus(function() {
    var target = $(this);
    window.setTimeout(function() { target.empty(); }, 10);
});
</script>

I developed a jQuery plug-in for this. Take a look https://github.com/phitha/editableDiv

Bass answered 16/4, 2015 at 16:18 Comment(0)
F
0
var curText = 'Edit me';
$('div').focusin(function() {
    if ($(this).text().toLowerCase() == curText.toLowerCase() || !$(this).text().length) {
        $(this).empty();
    }
}).focusout(function() {
    if ($(this).text().toLowerCase() == curText.toLowerCase() || !$(this).text().length) {
        $(this).html('<em>' + curText + '</em>');
    }
});
Femmine answered 1/2, 2012 at 9:47 Comment(1)
Thanks, but the problem is not that I don’t know how to save the placeholder text, but the fact that the caret disappears when clearing the placeholder text.Bergschrund
U
0

This is not exact solution of your problem ..

in summernote options set

airMode:true

placeholder works in this way.

Uredo answered 10/7, 2017 at 11:7 Comment(0)
P
0

In .css

.holder:before {
    content: attr(placeholder);
    color: lightgray;
    display: block;
    position:absolute;    
    font-family: "Campton", sans-serif;
}

in js.

clickedOnInput:boolean = false;
charactorCount:number = 0;
let charCount = document.getElementsByClassName('edit-box')[0];

if(charCount){
this.charactorCount = charCount.innerText.length;
}

if(charactorCount > 0 && clickedOnInput){
document.getElementById("MyConteditableElement").classList.add('holder');
}

if(charactorCount == 0 && !clickedOnInput){
document.getElementById("MyConteditableElement").classList.remove('holder');
}

getContent(innerText){
  this.clickedOnInput = false;
}

In .html

<div placeholder="Write your message.." id="MyConteditableElement" onclick="clickedOnInput = true;" contenteditable class="form-control edit-box"></div>

this solution worked for me in angular project

Poster answered 2/6, 2020 at 5:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.