72

For an application we're developing at the company where I work, we need an input that supports inserting emoticons inside our JS-based web app. We're currently using an input with the emoticon shortcodes (ie ':-)') and would like to switch to inserting actual, graphical images.

Our original plan was to use a contenteditable <div>. We're using listeners for the paste event as well as the different key/mouse interactions to ensure no unwanted markup enters the contenteditable (we strip text out of its container tags and leave only image tags that we inserted ourselves).

However, the problem right now is that the div resizes if you put in enough content (ie its height increases). We don't want this to happen, nor is it acceptable for the text to just be hidden (ie plain overflow: hidden). So:

Is there a way to make the contenteditable div behave like a single-line input?

I'd like it best if there is a relatively simple attribute/css property that I've missed that will do what I want, but if necessary CSS+JS suggestions will also be appreciated.

Gijs
  • 5,201
  • 1
  • 27
  • 42
  • Sorry, but I don't understand the question. `overflow:hidden` *does* make a div behave like a single-line input: http://jsfiddle.net/YLMK4/2/ (check it out in chrome and IE9 at least, and http://jsfiddle.net/YLMK4/1/ for a solution that works in FF. Of course you can integrate the two easily) So then what is it you're looking for? – davin Jul 26 '11 at 14:44
  • @davin: In (/2), text wraps for me. In (/1) a scrollbar appears and I cannot see what I'm typing. This is Fx 5.0.1 on Linux. – Gijs Jul 26 '11 at 16:02
  • I'm not happy with the scrollbar solution, so I'm going to try my hand at hacking up a JS+CSS solution that doesn't require a scrollbar, once I have time (live issues today...). If I fail, I'll probably set a bounty to see if someone else can come up with a functioning solution without a scrollbar. – Gijs Aug 01 '11 at 13:14
  • If you manage it, you could then post your final result here as the answer. I am really interested to see what you come up with. – tw16 Aug 04 '11 at 09:57
  • Sure! Right now I have something which works well in Fx5 and Chrome, using getBoundingClientRect and positioning the div inside a container. I still need to add some event handling for mouse-dragging the selection, then it will be more or less perfect... and then I'll have to check out what happens on IE. ;-) – Gijs Aug 05 '11 at 08:12
  • @tw16: done! I've stuck up the source in a gist, 'unfortunately' my product manager decided, upon seeing the degree of work required for single-line-ness, that maybe we should have a multiline editor, so the code has now been abandoned... – Gijs Aug 22 '11 at 08:30
  • @Gijs, I wrote big answer for this question https://stackoverflow.com/a/55950530/6243725. Can you please mark it as answer? – vitaliydev May 07 '19 at 20:53
  • @vitaliydev please use the 'snippet' functionality to post a functioning snippet that combines all these aspects of your answer. – Gijs May 08 '19 at 21:20
  • @Gijs, Thanks for suggestion, I added snippets. – vitaliydev May 10 '19 at 17:51
  • @Gijs, I also: 1) fixed problems with paddings. Now it is only one solution with correct borders and paddings. 2) added example of adding placeholder. It has no problems (solutions in other questions have problems). 3) added section with other little problems left. 4) added padding problems into solution with one div and "scrollbar-width". Please mark my post as answer. – vitaliydev May 11 '19 at 18:02

10 Answers10

129

[contenteditable="true"].single-line {
    white-space: nowrap;
    width:200px;
    overflow: hidden;
} 
[contenteditable="true"].single-line br {
    display:none;

}
[contenteditable="true"].single-line * {
    display:inline;
    white-space:nowrap;
}
<div contenteditable="true" class="single-line">
    This should work.
</div>​
JustCarty
  • 3,839
  • 5
  • 31
  • 51
Alessio
  • 1,314
  • 1
  • 9
  • 4
  • @Gijs Which browser are you using? edit: just noticed you mentioned it above (Firefox 5/Linux). – iDev247 Mar 23 '13 at 20:51
  • This is an excellent solution... i'm just wondering - what kind of tags are within the div that the * takes care of ?? If I knew what they were - then I'd use them specifically rather than use the * – Danield Sep 17 '13 at 06:26
  • 3
    Also, if you set a fixed height to the [contenteditable="true"].single-line class - then this prevents linebreaks even if user pastes formatted text inside – Danield Sep 17 '13 at 06:28
  • 4
    Note that the div will still contain the newlines, especially if you retrieve the innerHTML in javascript – Bilow Apr 16 '17 at 11:36
  • Perhaps add   or two for empty contenteditable [contenteditable="true"].single-line:empty:before { content: "\00a0\00a0" } – Miha Pirnat Oct 26 '17 at 07:35
  • 2
    Could you please explain your logic, Alessio? – Kalpesh Singh Dec 03 '17 at 14:11
  • You may want to set display:none on interior div tags as well. I noticed that when I hit enter/return on my keyboard a div tag is created with a br tag inside. Nice code! It's working well for me. – Frank Dec 29 '17 at 15:53
  • Hiding
    element is bad thing. Mozilla Firefox uses that element as workaround to solve caret (cursor) hiding bug after deleting last character. That's because when there is not content text cursor disappears.
    – vitaliydev May 02 '19 at 09:52
  • Also this solution does not work because user cannot scroll the line by selecting the text or by mouse (but can with keyboard arrows). – vitaliydev May 02 '19 at 09:59
  • Works in chrome not in FF – Aadam Jul 22 '19 at 15:34
  • is there no better way to do this in 2019?? – oldboy Oct 06 '19 at 22:50
  • Works in Chrome, Firefox and Safari. Great solution! – Souvik Ray Nov 14 '19 at 09:06
  • not recommended, it's just "appearing" to be single line, but adds tags in source – Nabeel Khan Mar 05 '20 at 20:14
9

Other answers are wrong and contain few mistakes (on 2019-05-07). Other solutions suggest to use "white-space: nowrap" (prevents carrying to another line) + "overflow: hidden" (prevents long text going beyond the field) + hiding <br> and other.

First mistake in that solutions is "overflow: hidden" also prevents scrolling the text. User will not be able to scroll the text by:

  • Pressing mouse middle button
  • Selecting the text and moving mouse pointer to the left or right
  • Using horizontal mouse scroll (when user have such a thing)

The only way he can scroll is using keyboard arrows.

You can solve this problem by using "overflow: hidden" and "overflow: auto" (or "scroll") at the same time. You should create parent div with "overflow: hidden" to hide content user should not see. This element must have input borders and other design. And you should create child div with "overflow-x: auto" and "contenteditable" attribute. This element will have scrollbar so user can scroll it without any limitations and he will not see this scrollbar because of hiding overflow in parent element.

Example of solution:

document.querySelectorAll('.CETextInput').forEach(el => {
 //Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
 el.parentNode.addEventListener('mousedown', function(e) {
  if (e.target === this) {
   setTimeout(() => this.children[0].focus(), 0);
  }
 });
 
 //Prevent Enter. See purpose in "Step 2" in answer.
 el.parentNode.addEventListener('keydown', function(e) {
  if (e.keyCode === 13)
   e.preventDefault();
 });
});
.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
 display: inline-block;
 border: 1px solid #aaa;
}

.CETextInputCont {
 overflow: hidden;
 cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
 
 /*Style:*/
 width: 10em;
 height: 1em;
 line-height: 1em;
 padding: 5px;
 font-size: 20px;
 font-family: sans-serif;
}

.CETextInput {
 white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
 overflow-x: auto;
 min-height: 100%; /*to prevent zero-height with no text*/
 
 /*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
 padding: 5px 0;
 margin-top: -5px;
 
 outline: none; /*Prevent border on focus in some browsers*/
}
<div class="CETextInputBorder">
 <div class="CETextInputCont">
  <div class="CETextInput" contenteditable></div>
 </div>
</div>


Step 2: Solving problem with <br> and other:

Also there is a problem that user or extensions can paste

  • <br> (can be pasted by user)
  • <img> (may have big size) (can be pasted by user)
  • elements with another "white-space" value
  • <div> and other elements that carry text to another line
  • elements with unsuitable "display" value

But advise to hide all <br> is wrong too. That is because Mozilla Firefox adds <br> element to empty field (I guess it may be workaround of bug with text cursor disappearing after deleting last character; checked in Firefox 66 released on 2019-03-19). If you hide this element then when user moves focus to field caret will be set in this hidden <br> element and text cursor will be hidden too (always).

You can fix this if you will be <br> when you know field is empty. You need some javascript here (you cannot use :empty selector because field contains <br> elements and not empty). Example of solution:

document.querySelectorAll('.CETextInput').forEach(el => {
 //OLD CODE:
 
 //Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
 el.parentNode.addEventListener('mousedown', function(e) {
  if (e.target === this) {
      setTimeout(() => this.children[0].focus(), 0);
    }
 });
 
 //Prevent Enter to prevent blur on Enter
 el.parentNode.addEventListener('keydown', function(e) {
  if (e.keyCode === 13)
   e.preventDefault();
 });
 
 //NEW CODE:
 
 //Update "empty" class on all "CETextInput" elements:
 updateEmpty.call(el); //init
 el.addEventListener('input', updateEmpty);

 function updateEmpty(e) {
  const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
  this.classList.toggle('empty', !s);
 }
});
/*OLD CODE:*/

.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
 display: inline-block;
 border: 1px solid #aaa;
}

.CETextInputCont {
 overflow: hidden;
 cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
 
 /*Style:*/
 width: 10em;
 height: 1em;
 line-height: 1em;
 padding: 5px;
 font-size: 20px;
 font-family: sans-serif;
}

.CETextInput {
 white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
 overflow-x: auto;
 min-height: 100%; /*to prevent zero-height with no text*/
 
 /*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
 padding: 5px 0;
 margin-top: -5px;
 
 outline: none; /*Prevent border on focus in some browsers*/
}

/*NEW CODE:*/

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
 display: none;
}

.CETextInput * {
 display: inline;
 white-space: pre;
}
<!--OLD CODE:-->

<div class="CETextInputBorder">
 <div class="CETextInputCont">
  <div class="CETextInput" contenteditable></div>
 </div>
</div>


Step 3: Solving problem with getting value:

We hided <br> elements so "innerText" value will not contain them. But:

  1. When "empty" class is set result may contain <br> elements.
  2. Your other styles or extensions may override "display: none" by "!important" mark or by rule with higher priority.

So when you get value you should make replace to avoid accidental getting line breaks:

s = s.replace(/[\r\n]+/g, '');


Do not use javascript for hiding <br>

Also you could solve the problem with <br> by removing them by javascript but this is very bad solution because after every removing user cannot use "undo" action anymore for canceling changes was made before removing.

Also you could use document.execCommand('delete') to delete <br> but it is hard to implement + user can undo your deletion and restore <br> elements.


Adding placeholder

It was not asked in question but I guess many people using single-line contenteditable elements will need it. Here is example how to make placeholder using css and "empty" class we talked above:

//OLD CODE:

document.querySelectorAll('.CETextInput').forEach(el => {
 //Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
 el.parentNode.addEventListener('mousedown', function(e) {
  if (e.target === this) {
      setTimeout(() => this.children[0].focus(), 0);
    }
 });
 
 //Prevent Enter to prevent blur on Enter
 el.parentNode.addEventListener('keydown', function(e) {
  if (e.keyCode === 13)
   e.preventDefault();
 });
 
 //Update "empty" class on all "CETextInput" elements:
 updateEmpty.call(el); //init
 el.addEventListener('input', updateEmpty);

 function updateEmpty(e) {
  const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
  this.classList.toggle('empty', !s);
  
  //NEW CODE:
  
  //Make element always have <br>. See description in html. I guess it is not needed because only Firefox has bug with bad cursor position but Firefox always adds this element by itself except on init. But on init we are adding it by ourselves (see html).
  if (!s && !Array.prototype.filter.call(this.children, el => el.nodeName === 'BR').length)
   this.appendChild(document.createElement('br'));
 }
});
/*OLD CODE:*/

.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
 display: inline-block;
 border: 1px solid #aaa;
}

.CETextInputCont {
 overflow: hidden;
 cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
 
 /*Style:*/
 width: 10em;
 height: 1em;
 line-height: 1em;
 padding: 5px;
 font-size: 20px;
 font-family: sans-serif;
}

.CETextInput {
 white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
 overflow-x: auto;
 min-height: 100%; /*to prevent zero-height with no text*/
 
 /*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
 padding: 5px 0;
 margin-top: -5px;
 
 outline: none; /*Prevent border on focus in some browsers*/
}

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
 display: none;
}

.CETextInput * {
 display: inline;
 white-space: pre;
}

/*NEW CODE:*/

.CETextInput[placeholder].empty::before { /*Use ::before not ::after or you will have problems width first <br>*/
 content: attr(placeholder);
 display: inline-block;
 width: 0;
 white-space: nowrap;
 pointer-events: none;
 cursor: text;
 color: #b7b7b7;
 
 padding-top: 8px;
 margin-top: -8px;
}
<!--OLD CODE:-->

<div class="CETextInputBorder">
 <div class="CETextInputCont">
  <div class="CETextInput" placeholder="Type something here" contenteditable><br></div>
 </div>
</div>

<!--We manually added <br> element for Firefox browser because Firefox (tested on 2019-05-11, Firefox 66) has bug with bad text cursor position in empty contenteditable elements that have ::before or ::after pseudo-elements.-->


Solution with only one div and "scrollbar-width"

You can also use only one div by setting "overflow-x: auto", "overflow-y: hidden" and "scrollbar-width: none". But "scrollbar-width" is new property and works only in Firefox 64+ and no other browsers yet.

You can also add:

  • webkit-prefixed version: "-webkit-scrollbar-width: none"
  • non-standardized ".CETextInput::-webkit-scrollbar { display: none; }" (for webkit-based browsers)
  • "-ms-overflow-style: none"

I would not recommend to use this solution, but here is example:

//OLD CODE:

document.querySelectorAll('.CETextInput').forEach(el => {
 //Focusing on child is not needed anymore
 
 //Prevent Enter to prevent blur on Enter
 el.addEventListener('keydown', function(e) {
  if (e.keyCode === 13)
   e.preventDefault();
 });
 
 //Update "empty" class on all "CETextInput" elements:
 updateEmpty.call(el); //init
 el.addEventListener('input', updateEmpty);

 function updateEmpty(e) {
  const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
  this.classList.toggle('empty', !s);
 }
});
/*NEW CODE:*/

.CETextInput {
 white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
 overflow-x: auto; /*or "scroll"*/
 overflow-y: hidden;
 -webkit-scrollbar-width: none; /*Chrome 4+ (probably), webkit based*/
 scrollbar-width: none; /*FF 64+, Chrome ??+, webkit based, Edge ??+*/
 -ms-overflow-style: none; /*IE ??*/
 
 /*Style:*/
 width: 10em;
 height: 1em;
 line-height: 1em;
 padding: 5px;
 border: 1px solid #aaa;
 font-size: 20px;
 font-family: sans-serif;
}

.CETextInput::-webkit-scrollbar {
 display: none; /*Chrome ??, webkit based*/
}

/*OLD CODE:*/

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
 display: none;
}

.CETextInput * {
 display: inline;
 white-space: pre;
}
<!--NEW CODE:-->

<div class="CETextInput" contenteditable></div>

This solution has 3 problems with paddings:

  1. In Firefox (tested on 2019-05-11, Firefox 66) there is no right padding when long text is typed. That is because Firefox does not display bottom or right padding when using padding in the same element that have scrollbar and when content is scrolled to the end.
  2. In all browsers there is no padding when scrolling long text in middle position. It looks worse. <input type="text"> does not have this problem.
  3. When user press home or end browsers scroll to place paddings are not visible.

To solve these problems you need use 3 elements like we used before but in this case you don't need use scrollbar-width. Our solution with 3 elements does not have these problems.


Other problems (in every solution):

  • Blur on pasting text ends with line break. I will think how to fix it.
  • When using paddings this.children[0].focus() is not enough in webkit-based browsers (cursor position is not where user clicked). I will think how to fix it.
  • Firefox (tested on 2019-05-11, Firefox 66): When short text is typed user cannot select last word by double clicking on the right of it. I will think about it.
  • When user starts text selection in the page he can end it in our field. Usual <input type="text"> does not have this behavior. But I don't think it is critical.
vitaliydev
  • 420
  • 4
  • 7
  • So you asked me to mark this as accepted, but this solution still has a horizontal scrollbar (Firefox 67 beta, macOS). – Gijs May 15 '19 at 09:56
  • @Gijs, I installed Firefox 67 beta, there is no scrollbars in my solutions. Please send me usercss you use (or css from Stylish of you use it). Also maybe you used solution with only one
    ? It has no scrollbars only in Firefox 64+ (but Firefox 67 is bigger than Firefox 64, but maybe you used Firefix 63 or lower).
    – vitaliydev May 15 '19 at 13:57
  • At time of writing, I work for Mozilla, and have done for the past few years. I know which Firefox version I'm using, thanks. I don't use any user CSS or stylish. I used the first snippet in your answer. I'm on mac, scrollbars are set to auto-hide (the default). Screenshot: https://imgur.com/a/AkupcSh . – Gijs May 17 '19 at 11:47
  • Appreciate the comprehensive answer; missing outstanding issue: pasting formatted text. http://s.webcore.io/a1033aac7057/its-so-green.png – amcgregor Jun 28 '19 at 19:26
  • Awesome! Just freaking awesome!! You solved the problem I was having for the last two weeks, i.e. mouse scroll to select text. Can't thank you enough. <3 – Gogol Jun 09 '20 at 10:53
6

I think you are looking for a contenteditable div with only one line of text that scrolls horizontally when it overflows the div. This should do the trick: http://jsfiddle.net/F6C9T/1

div {
    font-family: Arial;
    font-size: 18px;
    min-height: 40px;
    width: 300px;
    border: 1px solid red;
    overflow: hidden;
    white-space: nowrap;
}
<div contenteditable>
    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
</div>

The min-height: 40px incorporates the height for when the horizontal scroll bar appears. A min-height:20px would automatically expand when the horizontal scroll bar appears, but this doesn't work in IE7 (though you could use conditional comments to apply separate styling if you wanted it).

JustCarty
  • 3,839
  • 5
  • 31
  • 51
tw16
  • 29,215
  • 7
  • 63
  • 64
  • This is pretty good, but for esthetic reasons, I bet product management will hate the scrollbar. Any clever ideas on how to do it without one? ('no' is totally valid, but I'm still curious ;-) ). – Gijs Jul 27 '11 at 05:06
  • @gijs: So you want to scroll with the cursor instead of an actual scroll bar? Is there any reason you can't use ``? – tw16 Jul 27 '11 at 10:12
  • Yes, and yes: wanting to have the images inside the text input. :-) – Gijs Jul 27 '11 at 15:47
  • 1
    @gijs: There is no easy way to replicate the effect you are looking for. But from a usability point of view, the scroll bar is far better. This solution still achieves the ultimate aim of a single line non-wrapping `contenteditable` `div` that allows scrolling of the content. – tw16 Jul 28 '11 at 13:28
  • 1
    For future reference, it is possible to make it work without the scrollbar. Simply make `overflow-x:hidden;` and then it will scroll the text with the cursor, without showing a scrollbar. – BurningLights Sep 16 '15 at 20:37
  • 2
    The solution does not work because user cannot scroll the line by selecting the text or by mouse middle button (but can with keyboard arrows). – vitaliydev May 02 '19 at 10:02
4

I adapted the @tw16 accepted solution (on 5th Dec 2019) to add in scrolling. The trick was to add scrolling using overflow-x: auto but then hide the scrollbar (https://stackoverflow.com/a/49278385)

/* Existing Solution */
[contenteditable="true"].single-line {
    white-space: nowrap;
    width: 200px;
    overflow: hidden;
} 
[contenteditable="true"].single-line br {
    display:none;

}
[contenteditable="true"].single-line * {
    display:inline;
    white-space:nowrap;
}

/* Make Scrollable */
[contenteditable="true"].single-line {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none;  /* Internet Explorer 10+ */
}
[contenteditable="true"].single-line::-webkit-scrollbar { /* WebKit */
    width: 0;
    height: 0;
}    
<div contenteditable="true" class="single-line">
    This should scroll when you have really long text!
</div>​
ptimson
  • 5,533
  • 8
  • 35
  • 53
3

Here's a relatively simple solution that uses the contenteditable's input event to scan the dom and filter out various flavors of new lines (so it should be robust against copy/paste, drag 'n drop, hitting enter on the keyboard, etc). It condenses multiple TextNodes into single TextNodes, strips newlines from TextNodes, kills BRs, and puts a "display: inline" on any other element that it touches. Tested on Chrome, no guarantees anywhere else.

var editable = $('#editable')

editable.on('input', function() {
  return filter_newlines(editable);
});


function filter_newlines(div) {
    var node, prev, _i, _len, _ref, _results;
    prev = null;
    _ref = div.contents();
    _results = [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        node = _ref[_i];
        if (node.nodeType === 3) {
            node.nodeValue = node.nodeValue.replace('\n', '');
            if (prev) {
                node.nodeValue = prev.nodeValue + node.nodeValue;
                $(prev).remove();
            }
            _results.push(prev = node);
        } else if (node.tagName.toLowerCase() === 'br') {
            _results.push($(node).remove());
        } else {
            $(node).css('display', 'inline');
            filter_newlines($(node));
            _results.push(prev = null);
        }
    }
    return _results;
}
#editable {
    width: 200px;
    height: 20px;
    border: 1px solid black;
}
<div id="editable" contenteditable="true"></div>

Or here's the fiddle: http://jsfiddle.net/tG9Qa/

JustCarty
  • 3,839
  • 5
  • 31
  • 51
josh
  • 9,038
  • 8
  • 31
  • 37
  • 1
    The `input` event is not universally supported on contenteditable, unfortunately. IE and Opera don't support it at all and Firefox has only had it since version 14. – Tim Down Aug 14 '12 at 10:30
  • What Tim said. But ideally, this kind of thing would be more sensible than having to deal with the current myriad of events, dom quirks, etc. (of course, your current code doesn't do the latter, either, but it's probably an OK start for someone who can afford to put rather strict requirements on the browsers used). – Gijs Aug 14 '12 at 10:46
2

If you want a different way of solving it other than changing the requirements, with a little display:table it is fully possible =)

.container1 {
    height:20px;
    width:273px; 
    overflow:hidden;
    border:1px solid green;
}
.container2 {
    display:table;
}
.textarea {
    width:273px;
    font-size: 18px;
    font-weight: normal;
    line-height: 18px;
    outline: none;
    display: table-cell;
    position: relative;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
    user-select: text;
    word-wrap: break-word;    
    overflow:hidden;
}
<div class="container1">
    <div class="container2">
        <div contenteditable="true" class="textarea"></div>
    </div>
</div>
sergeykish
  • 181
  • 1
  • 10
1

Check out this answer I just posted. This should help you out:

How to create a HTML5 single line contentEditable tab which listens to Enter and Esc

Here is the HTML markup:

<span contenteditable="false"></span>

Here is the jQuery/javascript:

$(document).ready(function() {
    $('[contenteditable]').dblclick(function() {
        $(this).attr('contenteditable', 'true');
        clearSelection();
        $(this).trigger('focus');
    });

    $('[contenteditable]').live('focus', function() {
        before = $(this).text();
        if($(this).attr('contenteditable') == "true") { $(this).css('border', '1px solid #ffd646'); }
    //}).live('paste', function() {
    }).live('blur', function() {
        $(this).attr('contenteditable', 'false');
        $(this).css('border', '1px solid #fafafa');
        $(this).text($(this).text().replace(/(\r\n|\n|\r)/gm,""));

        if (before != $(this).text()) { $(this).trigger('change'); }
    }).live('keyup', function(event) {
        // ESC=27, Enter=13
        if (event.which == 27) {
            $(this).text(before);
            $(this).trigger('blur');
        } else if (event.which == 13) {
            $(this).trigger('blur');
        }
    });

    $('[contenteditable]').live('change', function() {
        var $thisText = $(this).text();
        //Do something with the new value.
    });
});

function clearSelection() {
    if ( document.selection ) {
        document.selection.empty();
    } else if ( window.getSelection ) {
        window.getSelection().removeAllRanges();
    }
}

Hope this helps someone!!!

Community
  • 1
  • 1
Anthony Graglia
  • 5,355
  • 5
  • 46
  • 75
  • (copied from your original post): This is not enough, because I can still copy and paste into this textbox, which totally confuses your code and shows newlines anyway. Furthermore, your code strips all HTML (by using only 'text') - so I'm in fact not quite sure why you don't just use a textbox. – Gijs Jun 28 '12 at 08:08
  • You can also use .html(). This is for text which is on a tab or something so you dont want it as a textbox. Only once they double-click, can you then edit it. As for 'paste', there is a commented line for that too. You can handle it as you please. – Anthony Graglia Jun 28 '12 at 09:41
1

So, for posterity: the simplest solution is to get your product manager to change the requirements so you can do multiline editing. This is what ended up happening in our case.

However, before that happened, I ended up going quite a way in creating a manually moving single-line richtext editor. I wrapped it up in a jQuery plugin in the end. I don't have time to finish it up (there are probably bugs in IE, Firefox works best and Chrome works pretty well - comments are sparse and sometimes not very clear). It uses parts of the Rangy library (extracted because I didn't want to rely on the complete library) to get screen positions of selections in order to test for mouse position vs. selection (so you can drag selections and move the box).

Roughly, it works by using 3 elements. An outer div (the thing you call the plugin on), which gets overflow: hidden, and then 2 levels of nesting inside it. The first level is absolutely positioned, the second level is the actual contenteditable. This separation is necessary because otherwise some browsers will give the contenteditable absolutely positioned element grippies, to let the user move it around...

In any case, then there is a whole bunch of code to move the absolutely positioned element around inside the top element, thus moving the actual contenteditable. The contenteditable itself has white-space nowrap, etc. etc. to force it to stay a single line.

There is also code in the plugin that strips out anything that isn't an image (like br, tables, etc. etc.) from any text that's pasted / dragged into the box. You need some parts of this (like the brs, stripping/normalizing paragraphs, etc.) but maybe you would normally want to keep links, em, strong and/or some other formatting.

Source: https://gist.github.com/1161922

Gijs
  • 5,201
  • 1
  • 27
  • 42
0

You can replace this div with text input (after onclick event is called).
I have used something similar to this plugin and it worked fine.

shaggy
  • 1,708
  • 2
  • 15
  • 17
  • This won't let the user continue typing/moving/selecting past images, so that will not do for our application. It should really function like a complete, one-line input box. – Gijs Jul 26 '11 at 16:04
-3

with jQuery I have set a .keypress event and then tests for e.keyCode == 13 (return key) and if is return false from the event and the editing is not able to make multilines

$('*[contenteditable=yes]').keypress(function(e) {
  if(e.keyCode == 13 && !$(this).data('multiline')) {
    return false;
  }
})
Hans
  • 1
  • 3
    This won't work. There are many ways people can insert newlines, not just by pressing keys (think of drag and drop, copy paste, etc.). Furthermore, the `contenteditable` attribute takes `true`, `false`, `inherit` or the empty string as a value - not `yes`, at least according to the spec. I also suspect (but haven't checked) suppressing `keypress` won't necessarily work cross browser with the return key, and would use `keydown` and/or `keyup` instead. – Gijs Feb 03 '12 at 10:33