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.
[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>
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:
When "empty" class is set result may contain <br> elements.
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:
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.
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.
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.
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).
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>
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/
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>
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
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!!!
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.
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;
}
})
Related
I have a textarea and when I type something, for some words the color should change.
For example, if the typed text is next one: He went to the market to buy an apple
The "market" word should become green
The "apple" word should become red
This is my current code:
var str = 'market';
var value = str.includes('market');
if (value == str) {
document.getElementById("text").style.color = "green";
} else {
document.getElementById("text").style.color = "red";
}
<textarea rows="9" cols="100" id="text" onClick="changeText();"></textarea>
Unfortunately, you can't add markup inside a textarea, but here is an idea you could take as a starting approach, it comes from this link. The approach will be based on this:
The basic idea is to carefully position a <div> behind the <textarea>. Then JavaScript will be used to copy any text entered into the <textarea> to the <div>. A bit more JavaScript will make that both elements scroll as one. With everything perfectly aligned, we can add markup inside the <div> to give colors to some particular words, and we going to set text color to transparent on the <textarea>, completing the illusion.
Base Implementation:
// Initialization.
const colorMap = {"apple": "red", "market": "green", "banana": "orange"};
let textArea = document.getElementById("myTextArea");
let customArea = document.querySelector(".custom-area");
let backdrop = document.querySelector(".backdrop");
// Event listeners.
textArea.addEventListener("input", function()
{
customArea.innerHTML = applyColors(textArea.value);
});
textArea.addEventListener("scroll", function()
{
backdrop.scrollTop = textArea.scrollTop;
});
function applyColors(text)
{
let re = new RegExp(Object.keys(colorMap).join("|"), "gi");
return text.replace(re, function(m)
{
let c = colorMap[m.toLowerCase()];
return `<spam style="color:${c}">${m}</spam>`;
});
}
.backdrop, #myTextArea {
font: 12px 'Open Sans', sans-serif;
letter-spacing: 1px;
width: 300px;
height: 100px;
}
#myTextArea {
margin: 0;
position: absolute;
border-radius: 0;
background-color: transparent;
color: transparent;
caret-color: #555555;
z-index: 2;
resize: none;
}
.backdrop {
position: absolute;
z-index: 1;
border: 2px solid transparent;
overflow: auto;
pointer-events: none;
}
.custom-area {
white-space: pre-wrap;
word-wrap: break-word;
}
<div class="container">
<div class="backdrop">
<div class="custom-area">
<!-- Cloned text with colors will go here -->
</div>
</div>
<textarea id="myTextArea"></textarea>
</div>
Note this is just a base approach to understand the underlying idea. But with some work on it, maybe you can get a generalized version. For example, by now, the textarea can't be resizable. But maybe you can detect that event and rezise the backdrop dinamically.
You can style the text in the textarea as a whole, but since a textarea does not have sub-elements such as or you cannot give separate text within that textarea separate styles.
on the other hand if you had a separate div displaying a copy of the text you could in the innerHTML of the div assign
apple
to replace the word apple in the .. but the text in the textarea would remain unchanged.. possibly overlay div on top of the textarea but hidden until text is entered in the textarea. Not certain of the precise code to do that piece or if it would work. but at least it is a viable logic chain that I hope may help you find a solution.
To color a specific word from that text you must wrap that word with a html tag. But textarea doesn't support html tag.
You can do it outside of the textarea field.
It works so far on using the contenteditable attribute on the <div> tag with the autogrow feature of a textbox. Also the height transition of it. It all works good, except for one thing, deleting characters, to be specific, a line, will not animate its height, unlike adding new lines. I have still a little knowledge on CSS.
.autogrow {
border: 1px solid rgb( 0, 0, 0 );
padding: 10px;
}
#keyframes line_insert {
from {
height: 0px;
}
to {
height: 20px;
}
}
.autogrow[contenteditable] > div {
animation-duration: 250ms;
animation-name: line_insert;
}
.autogrow[contenteditable] {
overflow: hidden;
line-height: 20px;
}
<div class="autogrow" contenteditable="true"></div>
When I press Shift + Enter, it doesn't animate either, it does well though while pressing Enter. Just the removing of lines and the Shift + Enter key combination while entering a new line is the problem.
How to make it work? Can it be done using pure CSS? Or adding a javascript function for it?
To avoid these issues, I personally use a solution not based on pure CSS animations / transitions which I found always have problems. For example, in your CSS implementation, there is a bounce back effect if using the Enter too fast (you can slow the animation down to see it better).
Moreover, new lines handling is different between browsers, some will add <div><br></div>, some versions of IE add only <br>, etc.
I've never been able to fix all these problems or found an implementation fixing all of these so I decided to not modify at all the behavior of the contenteditable, let the browser do is magic which works and instead, react to what's happening.
We don't even have to worry about keys events like Shift + Enter or events like deletion, etc., all of these are natively handled by the navigator.
I choose instead to use 2 elements: one for the actual contenteditable and one for the styling of my contenteditable which will be the one having height animations / transitions based on the actual height of the contenteditable.
To do that, I'm monitoring every events that can change the height of a contenteditable and if the height of my styling element is not the same, I'm animating the styling element.
var kAnimationSpeed = 125;
var kPadding = 10;
$('div[contenteditable]').on('blur keyup paste input', function() {
var styleElement = $(this).prev();
var editorHeight = $(this).height();
var styleElementHeight = styleElement.height();
if (editorHeight !== styleElementHeight - kPadding * 2) {
styleElement.stop().animate({ height: editorHeight + kPadding * 2 }, kAnimationSpeed);
}
});
.autogrowWrapper {
position: relative;
}
.autogrow {
border: 1px solid rgb(0, 0, 0);
height: 40px; /* line-height + 2 * padding */
}
div[contenteditable] {
outline: none;
line-height : 20px;
position: absolute;
top: 10px; /* padding */
left: 10px; /* padding */
right: 10px; /* padding */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="autogrowWrapper">
<div class="autogrow"></div>
<div contenteditable="true"></div>
</div>
It's kinda hacky, but it works.
First, modify your CSS
.autogrow {
border: 1px solid rgb( 0, 0, 0 );
padding: 10px;
}
#keyframes line_insert {
from {
height: 0px;
}
to {
height: 20px;
}
}
.autogrow[contenteditable] > div {
animation-duration: 250ms;
animation-name: line_insert;
}
.autogrow[contenteditable] {
overflow: hidden;
line-height: 20px;
}
Then add this jQuery that detects Shift + Enter events and appends a div whenever they occur
$(".autogrow").keydown(function(e){
if (e.keyCode == 13 && e.shiftKey || e.keyCode == 13)
{
$(this).animate({height: $(this).height()+20},200);
$(this).append('<div><br></div>');
}
});
And that should work.
Check fiddle https://jsfiddle.net/wx38rz5L/582/
I am trying to create a div and show a timeout message in there. But it actually distorts other parts of Page. For eg see below. Session Timed out is the div with the message.
Now I don't want this to happen. PFB the JQuery code I am using to create this Div
function ShowSessionTimeOutDiv() {
var styler = document.createElement("div");
styler.setAttribute("style","font-size:15px;width:auto;height:auto;top:50%;left:40%;color:red;");
styler.innerHTML = "<b><i>Session TimedOut, Please refresh the Page</i></b>";
document.body.appendChild(styler);
var currentDiv = $('#GoToRequestControl1_UpdatePanel1').get(0);
currentDiv.parentNode.insertBefore(styler,currentDiv) ;
}
Am I missing something here? The Part in which this div is being displayed is coming from Master Page.
Have you tried the position:fixed styling on it in css, i did that on one of my websites and it didn't distort anything.
A page has a natural flow of its elements based on the default display rules specified by the W3C. When you add a div in between other elements it naturally affects the layout of the page; the positions of the other elements.
In order to drop in a new element without it affecting other elements you have to either reserve space for it, or take it out of the normal page flow.
There are a couple of ways to take an element out of the flow — you can float it, float:left or float:right, which is great, for example, to stack blocks on the left (instead of top-down) and let them wrap to new rows as available width changes. Using a flex layout gives you a lot of control also. But in this case of one thing popping up, changing the positioning of the new element is the most straightforward and can let you put the block exactly where you want it.
I have a demonstration and full explanation in a fiddle showing several examples along the way to getting what you want.
Basically, styling is needed to reposition the timeout message element that you're inserting. Styling is better done with CSS styles, compared to adding a bunch of inline styles. If I put my timeout popup message in a "messagebox" I can make a class for it.
/* Your styles, plus a couple extra to make the example stand out better */
div.messagebox {
font-size: 16px;
width: auto;
height: auto;
top: 40%;
left: 30%;
background-color: white;
border: 2px solid black;
}
Likewise, style the message itself with a class, instead of using inline styles and the deprecated presentational tags <b> and <i>.
/* I want the message in a messagebox to be bold-italic-red text. */
div.messagebox .message {
color: red;
font-style: italic;
font-weight: bold;
}
The big difference is that we will change the positioning of the element from the default static to instead use absolute positioning:
/* I don't really recommend a class called "positioned".
A class should describe the kind of thing the element *is*
not how it *looks*
*/
div.messagebox.positioned {
position: absolute;
width: 40%;
padding: 1.5em;
}
/* The container of the positioned element also has to be positioned.
We position it "relative" but don't move it from its natural position.
*/
section#hasposition {
position: relative;
}
The term "absolute" is tricky to learn ... the element being positioned is given an absolute position within its container, in a sense it's positioned relative to its container... but what position:relative means is relative to its own natural position, so it's easy to get confused at first over whether you want absolute or relative positioning.
Putting it all together, we have some basic HTML that represents major portions of a page — a real page will have far more, but those should be contained within some top-level containers. This shows only those top-level containers.
Then we have some javascript that will add the new element at the appropriate time. Here I just call the function to add it after a delay created with setTimeout(). I'm using full-on jQuery since you're using some in your example, and it makes the javascript more portable and more concise.
function ShowSessionTimeoutStyled() {
var styler = $('<div>').addClass('messagebox').addClass('positioned');
styler.html('<span class="message">The Session Timed Out</span>');
$('#hasposition .above').after(styler);
}
// wait 6 seconds then add the new div
setTimeout(ShowSessionTimeoutStyled, 6000);
div.messagebox {
font-size: 16px;
width: auto;
height: auto;
top: 20%;
left: 20%;
background-color: white;
border: 2px solid black;
}
div.messagebox .message {
color: red;
font-style: italic;
font-weight: bold;
}
div.messagebox.positioned {
position: absolute;
width: 40%;
padding: 1.5em;
}
section#hasposition {
position: relative;
}
/* also style some of the basic parts so you can see them better in the demonstration */
section.explanation {
margin: 1em 0.5em;
padding: 0.5em;
border: 1px solid gray;
}
.demonstration {
margin-left: 1em;
padding: 1em;
background-color: #e0e0e0;
}
.demonstration .above {
background-color: #fff0f0;
}
.demonstration .middle {
background-color: #f0fff0;
}
.demonstration .below {
background-color: #f0f0ff;
}
.demonstration footer {
background-color: white;
}
p {
margin-top: 0;
padding-top: 0;
}
section {
font-family: sans-serif;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="explanation">
<p>Here, a div is added dynamically, after the "basic part above", but the added div is <em>positioned</em>. You can see the other content isn't affected.</p>
<section class="demonstration" id="hasposition">
<div class="above">Basic part above</div>
<div class="middle">Middle part</div>
<div class="below">Part Below</div>
<footer>This is the page footer</footer>
</section>
</section>
I highly recommend the site Position Is Everything for articles and tutorials on positioning. Some of its other content is outdated — who needs to make PNGs to do drop-shadows any more? — but the way positioning works hasn't changed.
When you open this page (see Live demo) with Chrome :
<span id="myspan" contenteditable=true></span>
CSS :
#myspan { border: 0; outline: 0;}
JS :
$(myspan).focus();
the contenteditable span has focus (you can start to write things and you will see that it already had focus), but we don't see the "I" edit cursor.
How to make that this cursor is displayed ? (Remark : outline:0 is needed, as well as the fact that the span is empty even with no white space).
Note : With Firefox, the cursor is displayed.
The problem is that spans are inline elements. Just add display:block; to your CSS and it will fix the problem.
$(myspan).focus();
#myspan {
border: 0;
outline: 0;
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<span id="myspan" contenteditable=true></span>
I added padding to the left and the cursor appears.
#myspan
{
border: 0;
outline: 0;
min-width: 100px;
height: 30px;
padding-left: 1px;
}
Demo in jsFiddle
.cont_edit {
outline: 1px solid transparent;
}
This just has to do with the way an empty ContentEditable area is rendered. To prove it's not about the focus, add some text to your editable div, and then delete it. When the last character is gone, the cursor will disappear
From the question Setting the caret position to an empty node inside a contentEditable element
The selection/range model is based around indexes into text content, disregarding element boundaries. I believe it may be impossible to set the input focus inside an inline element with no text in it. Certainly with your example I cannot set focus inside the last element by clicking or arrow keys.
It almost works if you set each span to display: block, though there's still some highly strange behaviour, dependent on the existence of whitespace in the parent. Hacking the display to look inline with tricks like float, inline-block and absolute position make IE treat each element as a separate editing box. Relative-positioned block elements next to each other work, but that's probably impractical.
You could also try adding a zero-width character like
document.getElementById('myspan').focus();
#myspan {
border: 0;
outline: 0;
}
<span id="myspan" contenteditable="true"></span>
The solution was to change <span> to <div> (I've seen that this solves many contenteditable problems in other questions here and there) + to add a min-width.
Indeed, with the following code, the size of the <div> would be 0px x 18px ! That explains why the caret (edit cursor) would be hidden !
HTML
<div id="blah" contenteditable=true></div>
CSS
#blah {
outline: 0;
position: absolute;
top:10px;
left:10px;
}
JS
$("#blah").focus();
Then, adding
min-width: 2px;
in the CSS will allow the caret to be displayed, even with Chrome : http://jsfiddle.net/38e9mkf4/2/
The issue I faced on Chrome v89.0.4389.90 was that contenteditable fields would sometimes show the blinking caret on focusin and sometimes not. I noticed it always blinks when there's already content in the field before focusing. It's when there's no content that the sometimes will/won't behavior occurs.
At first, I thought there must be some conflicting event handler that's erratically taking focus away. I disabled all my event binds and timers. Still the same erratic behavior. Then I thought it might be some conflicting CSS, so I disabled all stylesheets. At least now the behavior was consistent: the caret blinks 100% of the time when the field has content; the caret does not blink 100% of the time when the field has no content.
I enabled binds and stylesheets again. My div was already set to display: block; with min-width, min-height, and padding set in the final computed style set. None of the other answers here worked. I do have a placeholder on :empty:before that was a possible culprit. I commented that out. Now the behavior was consistent again, same as if the stylesheet was off. Oddly enough, the runnable snippet on SO works with the same computed CSS stack. I want to keep the placeholder, so it requires further research with my actual codebase...
The only solution I could get to work 100% of the time with my current issue involved forcibly placing the caret inside empty fields by creating a blank space and removing it immediately afterwards. Best I can do for a workaround until debugging the root cause.
//force caret to blink inside masks
let force_caret = function() {
if (!this.textContent) {
this.textContent = ' ';
let r = document.createRange(),
s = window.getSelection();
r.setStart(this.childNodes[0], 0);
r.collapse(true);
s.removeAllRanges();
s.addRange(r);
this.textContent = '';
}
}
//binds
let els = document.querySelectorAll("[contenteditable]");
for (let i = 0; i < els.length; i++) {
els[i].addEventListener('focusin', force_caret, false);
}
/* styles irrelevant to the issue, added for visual assist */
:root {
--b-soft: 1px solid silver;
--bs-in: inset 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px rgba(255, 255, 255, 0.1);
--c-soft: gray;
--lg-warm: linear-gradient(30deg, rgb(254, 250, 250), #eedddd);
}
body {
font-family: system-ui, -apple-system, -apple-system-font, 'Segoe UI', 'Roboto', sans-serif;
}
[contenteditable] {
outline: initial;
}
[contenteditable][placeholder]:empty:before {
content: attr(placeholder);
color: var(--c-soft);
background-color: transparent;
font-style: italic;
opacity: .5;
font-size: .9em;
}
.input {
border-bottom: var(--b-soft);
padding: .2em .5em;
}
.input_mask {
display: flex;
align-items: baseline;
color: var(--c-soft);
}
.mask {
box-shadow: var(--bs-in);
border-radius: .2em;
background: var(--lg-warm);
font-weight: 500;
border: 1px solid transparent;
text-transform: uppercase;
/* styles possibly relevant to the issue according to other proposed solutions */
margin: 0 .4em .1em .4em;
padding: .2em .4em;
min-width: 3em;
min-height: 1em;
text-align: center;
}
<div data-type="tel" data-id="phone" class="input input_mask">
<span>+1 (</span>
<div maxlength="3" contenteditable="true" placeholder="111" class="mask"></div>
<span>)</span>
<div maxlength="3" contenteditable="true" placeholder="111" class="mask"></div>
<span>-</span>
<div maxlength="4" contenteditable="true" placeholder="1111" class="mask"></div>
<span>x</span>
<div maxlength="5" contenteditable="true" class="mask"></div>
</div>
Add a CSS style of
min-height: 15px;
you may also need
display: block;
to your contenteditable="true" tag
For me setting it content of contenteditable div to <br> works. I tried setting it to nbsp; but that creates extra character space in the div before i start editing. So, i choose this:
<div id="blah" contenteditable=true><br></div>
over:
<div id="blah" contenteditable=true>nbsp;</div>
Hope this helps.
I use Chrome and your Code works fine.
Try to use cursor: text; in your CSS. See here
When dragging and entering the <div class="upload-cont"> the color changes perfectly from gray to black of border and text and when it comes to the <span class="add-text"> it changes back to gray.
CSS:
.upload-cont{
cursor:pointer;
margin-left:130px;
display:inline-block;
border:2px dashed #a8a8a8;
max-width:220px;
max-height:180px;
min-width:220px;
min-height:180px;
position:relative;
border-radius:3px;
}
.add-text{
display:block;
font-size:10px;
font-weight:bold;
color:#999;
word-wrap:break-word;
text-align:center;
width:100px;
top:37%;
left:25%;
position:absolute;
}
.add-text:hover{ color:black; }
HTML:
<div class="upload-cont">
<span class="add-text">
Click to add or<br/>
Drag and drop image here
</span>
</div>
Jquery:
$(document).ready(function () {
$(".upload-cont,.add-text").on('dragenter', function (e) {
$(".upload-cont").css({
"border": "2px dashed black"
});
$(".add-text").css({
"color": "black"
});
});
$(".upload-cont").on('dragleave', function (e) {
$(".upload-cont").css("border", "2px dashed #a8a8a8");
$(".add-text").css({
"color": "#a8a8a8"
});
});
});
What can i do to remain the black color for the border and text when entering <span class="add-text">
Check this jsfiddle: http://jsfiddle.net/rpABs/
Thanks in advance
Use dragover instead of dragenter since dragleave fires when you enter child elements
$(".upload-cont,.add-text").on('dragover', function (e) {
$(".upload-cont").css({
"border": "2px dashed black"
});
$(".add-text").css({
"color": "black"
});
});
$(".upload-cont").on('dragleave', function (e) {
$(".upload-cont").css("border", "2px dashed #a8a8a8");
$(".add-text").css({
"color": "#a8a8a8"
});
});
DEMO
Apparently this problem is more recurrent than I thought since I found at least 5 questions associated with the same topic (and I will answer all related with this issue).
Unlike "mouseover", the events "dragover" and "dragleave" do not consider the child elements as a whole, so each time the mouse passes over any of the children, "dragleave" will be triggered.
Thinking about the upload of files, I created a widget that allows:
Drag and drop desktop files using $ _FILES
Drag and drop to browser images/elements or url using $ _POST and cURL
Attach a device file using button using $ _FILES
Use input to write/paste url images/elements using $ _POST and cURL
The problem: As everything, both form inputs and images, are within DIVs children, "dragleave" was triggered even if it did not leave the dashed line. Using the attribute "pointer-events: none" is not an alternative since methods 3 and 4 need to trigger "onchange" events.
The solution? An overlapping DIV that covers all the drop-container when the mouse enters, and the only one with child elements with "pointer-events: none".
The structure:
div #drop-container: main div, keep all togheter
div #drop-area: "dragenter" listener and inmediate trigger #drop-pupup
div #drop-pupup: at same leval as #drop-area, "dragenter", "dragleave" and "drop" listener
Then, when the mouse enters by dragging an element to #drop-area, inmediatly shows #drop-pupup ahead and successively the events are on this div and not the initial receiver.
Here is the JS/jQuery code. I took the liberty to leave the PoC so do not lose all the time I lost.
jQuery(document).on('dragover', '#drop-area', function(event) {
event.preventDefault();
event.stopPropagation();
jQuery('#drop-popup').css('display','block');
});
jQuery(document).on('dragover dragleave drop', '#drop-popup', function(event) {
event.preventDefault();
event.stopPropagation();
console.log(event.type);
// layout and drop events
if ( event.type == 'dragover') {
jQuery('#drop-popup').css('display','block');
}
else {
jQuery('#drop-popup').css('display','none');
if ( event.type == 'drop' ) {
// do what you want to do
// for files: use event.originalEvent.dataTransfer.files
// for web dragged elements: use event.originalEvent.dataTransfer.getData('Text') and CURL to capture
}
}
});
body {
background: #ffffff;
margin: 0px;
font-family: sans-serif;
}
#drop-container {
margin: 100px 10%; /* for online testing purposes only */
width: 80%; /* for jsfiddle purposes only */
display: block;
float: left;
overflow: hidden;
box-sizing: content-box;
position: relative; /* needed to use absolute on #drop-popup */
border-radius: 5px;
text-align: center;
cursor: default;
border: 2px dashed #000000;
}
#drop-area {
display: block;
float: left;
padding: 10px;
width: 100%;
}
#drop-popup {
display: none;
box-sizing: content-box;
position: absolute;
width: 100%;
top: 0;
left: 0;
background: linear-gradient(to BOTTOM, rgba(245, 245, 245, 1) , rgba(245, 245, 245, 0));
height: 512px;
padding: 20px;
z-index: 20;
}
#drop-popup > p {
pointer-events: none;
}
<html>
<head>
<title>Drag and Drop</title>
</head>
<body>
<div id="drop-container">
<div id="drop-area">
<p>Child paragraph content inside drop area saying "drop a file or an image in the dashed area"</p>
<div>This is a child div No. 1</div>
<div>This is a child div No. 2</div>
</div>
<div id="drop-popup">
<p>This DIV will cover all childs on main DIV dropover event and current P tag is the only one with CSS "pointer-events: none;"</p>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" type="text/javascript"></script>
</body>
<html>
About jQuery "on", use it with the div id inside on, so you can start event triggers starting "uploading box" hidden.
Finally, I preferred to use "dragover" over "dragenter" because it has a small delay (milliseconds) that favors performance
(https://developer.mozilla.org/en-US/docs/Web/API/Document/dragover_event).
The dragover event fires constantly as you're dragging, so I'm not a fan of that solution. I've written a little library called Dragster that gives me better enter & leave events.