blur event.relatedTarget returns null - javascript

I have an <input type="text"> field and I need to clear it when this field loses focus (whiech means that user clicked somewhere on the page). But there is one exception. Input text field should't be cleared when user clicks on a specific element.
I tried to use event.relatedTarget to detect if user clicked not just somewhere but on my specific <div>.
However as you can see in snippet below, it simply doesn't work. event.relatedTarget is always returning null!
function lose_focus(event) {
if(event.relatedTarget === null) {
document.getElementById('text-field').value = '';
}
}
.special {
width: 200px;
height: 100px;
background: #ccc;
border: 1px solid #000;
margin: 25px 0;
padding: 15px;
}
.special:hover {
cursor: pointer;
}
<input id="text-field" type="text" onblur="lose_focus(event)" placeholder="Type something...">
<div class="special">Clicking here should not cause clearing!</div>

Short answer: add tabindex="0" attribute to an element that should appear in event.relatedTarget.
Explanation: event.relatedTarget contains an element that gained focus. And the problem is that your specific div can't gain a focus because browser thinks that this element is not a button/field or some kind of a control element.
Here are the elements that can gain focus by default:
<a> elements with href attribute specified
<link> elements with href attribute specified
<button> elements
<input> elements that are not hidden
<select> elements
<textarea> elements
<menuitem> elements
elements with attribue draggable
<audio> and <video> elements with controls attribute specified
So event.relatedTarget will contain above elements when onblur happens. All other elements will are not counted and clicking on them will put null in event.relatedTarget.
But it is possible to change this behaviour. You can 'mark' DOM element as element that can gain focus with tabindex attribute. Here is what standart says:
The tabindex content attribute allows authors to indicate that an element is supposed to be focusable, whether it is supposed to be reachable using sequential focus navigation and, optionally, to suggest where in the sequential focus navigation order the element appears.
So here is the corrected code snippet:
function lose_focus(event) {
if(event.relatedTarget === null) {
document.getElementById('text-field').value = '';
}
}
.special {
width: 200px;
height: 100px;
background: #ccc;
border: 1px solid #000;
margin: 25px 0;
padding: 15px;
}
.special:hover {
cursor: pointer;
}
<input id="text-field" type="text" onblur="lose_focus(event)" placeholder="Type something...">
<div tabindex="0" class="special">Clicking here should not cause clearing!</div>

Related

How to dynamically append classes

It's been some years since I have had to do DOM manipulation with CSS and vanilla JavaScript.
I have an input element some default css that is being added to its wrapping div and not the input element itself like so:
<div class="field animated-label text required">
<label class="control-label" for="answer">Answer</label>
<input class="form-control" />
</div>
So the default css for this input element is dictated by this selector:
.rds-form .animated-label {
border: 2px solid #ccc;
border-radius: 2px;
color: #767676
display: block;
min-height: 40px;
position: relative;
}
However when the user clicks out of the input element not having typed anything in, this selector gets appended on to give a red error border around the input element:
.rds-form .field-group.error, .rds-form
.field.text.error, .rds-form
.field.select.error, .rds-form .field.textarea.error {
border: 2px solid #cc2233;
position: relative;
}
How would this be handled in JavaScript? I am assuming this is handled by some JavaScript logic.
Add two handlers to the <input> field:
// When the input field has focus, remove the 'error' class from .animated-label:
document.querySelector('input').addEventListener('focus', function(e) {
document.querySelector('.animated-label').classList.remove('error');
});
// When the input field loses focus, determine whether to add or remove the 'error' class:
document.querySelector('input').addEventListener('blur', function(e) {
if (e.target.value.match(/^\s*$/)) { // Input is empty
document.querySelector('.animated-label').classList.add('error');
} else {
document.querySelector('.animated-label').classList.remove('error');
}
});

Change class value javascript in event onclick?

hello I need to do it only in javascript (not in jquery)
How would you do something equivalent in javascript something like this in query?
<body>
<div class="wysiwyg">text......text...</div>
<div class="btn btn-primary verMas">Ver más</div>
<script>
$('.verMas').click(function(e){
e.preventDefault();
$('.wysiwyg').css('height','auto');
$(this).hide();
$('.layerMiddle').hide();
});
</script></body>
Your example suggests that you need to be able to
Select Items from the DOM
Add an on-click event listener
Modify the display state of elements
Looking at how you might achieve these in native JS then I would suggest the following:
document.querySelector()
This provides some similar functionality to JQuery's selectors - at least for this use case.
element.addEventListener('click', function)
This provides equivalent functionality as JQuery's $('').click(function)
element.style
This enables modification of the in-line style of the element so setting the style of display to none can achieve this
Putting these all together:
// use querySelector to get the .verMas element and add on-click event listener
document.querySelector('.verMas').addEventListener('click', e => {
// prevent default event
e.preventDefault()
// use querySelector to get the .wysiwyg element
// set the style.height parameter to auto
document.querySelector('.wysiwyg').style.height = 'auto'
// e.target is the element that this event was fired on
// to hide it, set the style.display parameter to none
e.target.style.display = 'none'
// use querySelector to get the .layerMiddle element
//hide the layerMiddle Element
document.querySelector('.layerMiddle').style.display = 'none'
})
div {
padding: 10px;
}
.btn{
display: inline-block;
padding: 6px 12px;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
border: 1px solid transparent;
border-radius: 4px;
color: #333;
background-color: #fff;
border-color: #ccc;
}
<div class="wysiwyg">text......text...</div>
<div class="layerMiddle">[Layer Middle]</div>
<div class="btn btn-primary verMas">Ver más</div>

Show div banner after onchange of any form elems

JS:
document.getElementsByTagName("input, select, textarea, option, optgroup, fieldset, label").onchange = function () {
var updateRate = document.querySelector('.updateRate');
updateRate.style.display = "block";
};
mark-up & styles:
<div class="updateRate">Update Rate</div>
<style>
.updateRate {
display:none;
top: 0px;
position: fixed;
width: 100%;
left: 0px;
z-index: 11111;
}
#rate, .updateRate {
background: #354563;
color: #ffffff;
cursor: pointer;
text-align: center;
padding: 10px;
}
</style>
The above is my attempt; but the banner is still not displaying after any form elements state change.
Update: So both SO answers below seem to be correct; but perhaps I didn't explain the context enough - now the 'banner' displays as soon as the form is beginning to be filled out the first time; the goal was for the banner to show after a user has gone back in and updated a form element (second time, or changing it from initial).
Context:
It is an inline quote tool; a quote will generate inline after form elements are filled out. I was trying to create a 'banner' that would que if a user has adjusted any web form element a second time. Sorry for the confusion.
You can't attach the event this way since the getElementsByTagName method accept one single tag name you could use the querySelectorAll() method instead to get the elements then loop through them using foreach and attach the event using addEventListener():
var fields = document.querySelectorAll("input, select, textarea, option, optgroup, fieldset, label");
[].forEach.call(fields, function(el) {
el.addEventListener('change', showUpdateRate, false);
});
function showUpdateRate() {
if (document.querySelector('.quote').textContent != "") {
document.querySelector('.updateRate').style.display = "block";
}
}
document.querySelector('.generate').addEventListener('click', function() {
var quote = document.querySelector('[name="type"]:checked').value;
document.querySelector('.quote').textContent = quote + " Quote";
});
.updateRate {
display: none;
top: 0px;
position: fixed;
width: 100%;
left: 0px;
z-index: 11111;
}
#rate,
.updateRate {
background: #354563;
color: #ffffff;
cursor: pointer;
text-align: center;
padding: 10px;
}
<div class="updateRate">Update Rate</div>
<br><br><br>
<form>
<input type="radio" name="type" value="Success">Success
<input type="radio" name="type" value="Motivation">Motivation
<input type="radio" name="type" value="Work">Work
<br><br>
<input type="button" class="generate" value="Generate">
</form>
<br>
<span class="quote"></span>
.getElementsByTagName() returns a node list (an array-like object), not a single element. As such, it doesn't have an onchange property to work with. After getting the node list, you'll need to loop over all the items in the list and set up the event handler for each, one at a time.
Also, .getElementsByTagName() only allows for a single tag name to be passed in, not a comma separated list. Additionally, it returns a "live node list", which has performance implications, so if you aren't dynamically adding/removing elements, you should avoid it and use .querySelectorAll() instead.
Now, option, optgroup, and label elements can only be changed via code, and don't emit or recieve a change event in the first place, so you actually don't want/need those included in your node list.
More comments about how to make your code more modern and organized inline below:
// Get this reference just once and cache it in a variable
var updateRate = document.querySelector('.updateRate');
// Gather up all the relevant elements into a node list
let elements = document.querySelectorAll("input, select, textarea, fieldset");
// Convert the node list into an Array so that .forEach()
// can safely be used to loop in all modern browsers
Array.prototype.slice.call(elements).forEach(function(element){
// Add event listeners the modern way, not with .onXyz properties
element.addEventListener("change", function () {
// Just remove the hidden class already applied to the element
// instead of working with inline styles
updateRate.classList.remove("hidden");
});
});
/*
This is applied to the "Update Rate" element in HTML by default.
It can be removed by the JavaScript when appropriate.
*/
.hidden { display:none; }
.updateRate {
top: 0px;
position: fixed;
width: 100%;
left: 0px;
z-index: 11111;
}
#rate, .updateRate {
background: #354563;
color: #ffffff;
cursor: pointer;
text-align: center;
padding: 10px;
}
<form>
<fieldset>
<legend>This is the legend</legend>
<input>
<select>
<option>choice 1</option>
<option>choice 2</option>
<option>choice 3</option>
</select>
</fieldset>
<textarea></textarea>
</form>
<!-- Set this element to be hidden by default -->
<div class="updateRate" class="hidden">Update Rate</div>

MouseEvent swallowed if element hidden by css

I have a element (it is an icon) with onClick event. I want to show that element only if certain criteria is met and I'm able to read that criteria and handle the show/hide completely from CSS.
If the element is visible and I click on it, I want it to make two actions:
trigger JS event,
hide the element.
But CSS hides the element somehow quicker than JS can respond and the event is simply not triggered.
I did not studied how the the event system in JS works, but it seems to me, that firstly the CSS is resolved and then the JS event system receives info about click event on certain x/y position, where the element is no more, so the event is not triggered.
I tried several CSS options to hide the element including this CSS properties:
display: block / none;
visibility: visible / hidden;
z-index: 1 / -1;
width|height: auto / 0;
top|left|right|bottom: 0 / -9999px;
If I hide the element with opacity: 1 / 0, the event is triggered, because the element remains clickable in place, where it resides, but that is also problem, because I do not want the element to be clickable if not visible.
Is there any hack, how to hide element via pure CSS and at the same time trigger event on it?
I tried delay the CSS with transition: all 160ms;, but the CSS rules, that hides the element, are instant (you cannot transition display, visibility or z-index) so this had no use.
I challenge this problem in Google Chrome 53.0.2785.116 platform Win10 x64
EDIT : JSBin
Problem with click is when the item is hidden, the click action can not be completed. So you can switch to mousedown instead of click
document.getElementById("test_click").addEventListener("mousedown", function(){
console.log("Clicked");
});
#test_container {
position: relative;
display: inline-block;
}
#test_click {
position: absolute;
display: none;
left: 100%;
top: 50%;
}
#test_input:focus + #test_click {
display: inline;
}
#test_input:focus + #test_click:hover {
color: dodgerblue;
cursor: pointer;
}
<div id="test_container">
<input type="text" id="test_input" placeholder="focus me...">
<span id="test_click">CLICK</span>
</div>
<h3>Click on the input and "CLICK" appears, click on "CLICK" and "CLICK" hides and no JS event is triggered although it is attached. Then, remove "display: none;" from CSS and try click again, the JS event is triggered.</h3>
or add css to keep element visible when hovered
document.getElementById("test_click").addEventListener("click", function(){
console.log("Clicked");
});
#test_container {
position: relative;
display: inline-block;
}
#test_click {
position: absolute;
display: none;
left: 100%;
top: 50%;
}
#test_click:hover,
#test_input:focus + #test_click {
display: inline;
}
#test_input:focus + #test_click:hover {
color: dodgerblue;
cursor: pointer;
}
<div id="test_container">
<input type="text" id="test_input" placeholder="focus me...">
<span id="test_click">CLICK</span>
</div>
<h3>Click on the input and "CLICK" appears, click on "CLICK" and "CLICK" hides and no JS event is triggered although it is attached. Then, remove "display: none;" from CSS and try click again, the JS event is triggered.</h3>

contenteditable single-line input

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;
}
})

Categories