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.

David Hellsing
  • 106,495
  • 44
  • 176
  • 212

11 Answers11

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)

James Donnelly
  • 126,410
  • 34
  • 208
  • 218
mrmoje
  • 3,714
  • 3
  • 20
  • 18
  • 1
    This would be exceptionally cool, except... can't get it to work. Can you post a working jsfiddle? – ccleve Aug 25 '13 at 19:15
  • @ccleve Check the my edit. Hope you dont mind my using codepen. Jsfiddle acted up on me severaly (and i find codepen having better features) – mrmoje Aug 26 '13 at 23:11
  • 7
    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. – ccleve Aug 27 '13 at 15:02
  • 1
    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. – mrmoje Aug 27 '13 at 21:48
  • @ccleve I agree; this is clearer to a reader of the html and easier to templatize. – amwinter Sep 30 '13 at 00:55
  • 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. – Tim Down Nov 13 '13 at 11:38
  • 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' – mrmoje Nov 14 '13 at 02:24
  • 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. – Sophie McCarrell Jan 10 '14 at 21:35
  • ~Jason McCarrell use 'display: inline-block' instead of 'display: inline' and it should work :) – factoradic Feb 05 '14 at 11:32
  • @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 ` `. So you can (in theory) modify the example to handle `:empty` with javascript. Holla if you need an example. [CC @TimDown] ` – mrmoje Feb 22 '14 at 10:02
  • 1
    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. – vsync Feb 24 '14 at 16:56
  • 1
    You're right @vsync! That's a very good find. Thank you for sharing. – mrmoje Feb 24 '14 at 18:36
  • @ccleve make sure there are no accidental spaces in your div tag. I had the problem where my generated code looked like `
    ` instead of `
    `
    – Dex Jan 15 '15 at 02:47
  • very nice solution. Thanks – nicolas Jun 23 '15 at 07:03
  • elegant - good solution. All you need is color:#a9a9a9; to further give that placeholder "feel". – MarzSocks Nov 27 '16 at 07:49
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.

Craig Stuntz
  • 125,891
  • 12
  • 252
  • 273
  • 1
    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. – Craig Stuntz Mar 27 '13 at 13:18
  • Does it work if I am setting `div` text from backend? In my case placeholder and my text are concated – demo Jun 13 '16 at 15:37
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);
    }
});
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • 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. – Andy E Feb 01 '12 at 10:25
  • Sweet! I was hoping you’d be around mr. RangeMaster! – David Hellsing Feb 01 '12 at 10:28
  • 1
    @AndyE: Thanks. There may be a simpler way. There ought to be, really, but I couldn't find one quickly. – Tim Down Feb 01 '12 at 10:31
  • 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... – David Hellsing Feb 01 '12 at 10:33
  • Awesome! You saved me a lot of headaches :) – johnjohn Feb 13 '12 at 13:20
24

just use css pseudo-classes.

span.spanclass:empty:before {content:"placeholder";}
amwinter
  • 3,121
  • 2
  • 27
  • 25
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

ramo
  • 945
  • 4
  • 12
  • 20
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

wp student
  • 755
  • 9
  • 24
  • Works. Tested in firefox, chrome, and Safari. – Joel Caton Mar 26 '16 at 22:20
  • Eye, but not Edge (cursor appears at the end) – cgat Nov 29 '17 at 01:24
  • As of August 2020 this works in Edge (macOS version anyway). – NetOperator Wibby Aug 08 '20 at 14:11
  • 2
    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. – Guganeshan.T Nov 03 '20 at 08:23
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/

surfs up
  • 73
  • 1
  • 1
  • 5
  • The `input` event is pretty new in contenteditable elements. It isn't supported at all in IE, for example. – Tim Down Nov 13 '13 at 11:23
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

Phitha
  • 81
  • 1
  • 3
0

This is not exact solution of your problem ..

in summernote options set

airMode:true

placeholder works in this way.

Ganesh S
  • 307
  • 2
  • 3
  • 9
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

Sahil Ralkar
  • 2,331
  • 23
  • 25
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>');
    }
});
thecodeparadox
  • 86,271
  • 21
  • 138
  • 164
  • 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. – David Hellsing Feb 01 '12 at 09:49