Javascript contenteditable error - javascript

This is my html code for displaying a editable textbox and is used as a "memo" element inside a webpage (inside a div tag).
contenteditable="true" name="choice1" class="textfield" max="872">Enter your Memo Here!
CSS:
.textfield {
max-width: 800px;
width: 800px;
border: 1px solid black;
padding: 8px;
margin: 20px;
float:right;
position: relative;
}
Javascript:
var textfields = document.getElementsByClassName("textfield");
for(i=0; i<textfields.length; i++){
textfields[i].addEventListener("keypress", function(e) {
if(this.innerHTML.length >= this.getAttribute("max")){
e.preventDefault();
return false;
}
}, false);
}
However when I fill the editable div area with alot of spaces (" "), it takes less than the declared 872 characters to reach the maximum limit. Does anyone know why and have a solution? Thanks.

Use textContent instead of innerHTML.
The textContent property sets or returns the textual content of the specified node, and all its descendants.
The innerHTML property sets or returns the HTML content (inner HTML) of an element.Which will include tags
In the below example I have set the max as 20
var textfields = document.getElementsByClassName("textfield");
for (i = 0; i < textfields.length; i++) {
textfields[i].addEventListener("keypress", function(e) {
if (this.textContent.length >= this.getAttribute("max")) {
e.preventDefault();
console.log('max reached') ;
return false;
}
}, false);
}
<div contenteditable="true" name="choice1" class="textfield" max="20">Enter your Memo Here!</div>

Related

HTML: how to prevent moving focus from element?

I have several elements with tabindex attribute. When I click on any area of the page outside of them, they lose focus.
Current Behaviour - In regular desktop applications, if an element is not focusable, clicking on it doesn't move focus from a previous focused element.
But In HTML it's not the case: my focusable elements always lose focus, even if I click on elements with no tabindex.
Required Behaviour - Is it possible to prevent the above behaviour in HTML? I want my elements to lose focus only when I click on other focusable elements like its having in desktop application as I mentioned above.
This is a sort of hack and can be implemented in a better way.
Logic
Create a global variable lastSelectedInput to store id of last visited element.
Add a class to define boundary.
Add a click event on body and if event.path does not contains boundary element, call focus of lastSelectedInput
JSFiddle
Code
(function() {
var lastSelectedInput = "";
function bodyClick(e) {
var inside = false;
for (var i in e.path) {
if (e.path[i].className == "content") inside = true;
}
if (!inside) {
document.getElementById(lastSelectedInput).focus();
}
}
function inputFocus(e) {
lastSelectedInput = e.target.id;
e.stopPropagation()
}
function registerEvents() {
document.getElementsByTagName("body")[0].addEventListener("click", bodyClick);
var inputs = document.getElementsByTagName("input")
for (var i = 0; i < inputs.length; i++) {
inputs[i].onfocus = inputFocus;
}
}
registerEvents();
})();
.content {
margin: 15px;
background: #ddd;
border: 2px solid gray;
border-radius: 4px;
height: 200px;
width: 200px;
padding: 5px;
}
<div>
<div class="content">
<input type="text" id="txt1">
<input type="text" id="txt2">
<input type="text" id="txt3">
<input type="text" id="txt4">
<input type="text" id="txt5">
</div>
</div>

How to use Jquery to remove an overflowing element?

Pretty simple problem, but I can't find a solution. This plugin claims to do it, but I can't get it to work on my site at all, not as a called script, not inline, nothing. So, I have two columns of divs, the ones on one side larger than the other. I have set it up so the second column container will match the height of the first (which is determined elsewhere and thus varies) and set it to overflow:hidden, but what I want to do do is to remove the overflowing divs entirely so it always ends on the last complete div. Here's the fiddle: https://jsfiddle.net/bw2v39ru/2/
This is the JS to equalize the heights $('.row2').css('height', $('.row1').height()+'px');
In that example, only two of he block2 spans should be visible and the overflowing ones removed completely instead of leaving half a block.
Try this: https://jsfiddle.net/bw2v39ru/9/
Besides the code below - you will have to e.g. insert a <br style="clear:both;" /> in the parent DIV since the children has float: left
$('.row2').css('height', $('.row1').height());
var maxHeight = $("#main").outerHeight();
$("#main span").each(function() {
var elm = $(this);
if (elm.offset().top + elm.height() > maxHeight)
elm.remove();
});
as promised, here is my answer. Custom build jsfiddle from pure JavaScript.
https://jsfiddle.net/www139/vjgnsrpg/
Here is a code snippit for you. It assumes that all of your block2 elements have a fixed height. Also I changed the .row1 and .row2 classes to ids to make the solution easier to create. Feel free to change it back but remember to use document.getElementsByClassName('class')[i] instead.
//make sure you execute this script onload inside a jquery document ready or window.onload
//get the rendered height of both rows
//enter margin for blocks here
//this also assumes that the height of your block1 and block2 elements are fixed
var margin = 5;
var rowOneHeight = document.getElementById('row1').offsetHeight;
//get height of block2 element including vertical margin (multiplied twice)
var blockTwoHeight = document.getElementById('row2').getElementsByClassName('block2')[0].offsetHeight + 2 * margin;
var howManyBlocksCanFit = Math.floor(rowOneHeight / blockTwoHeight);
var numberOfBlocks = document.getElementById('row2').getElementsByClassName('block2').length;
for (var i = 0; i != numberOfBlocks - howManyBlocksCanFit; i++) {
document.getElementById('row2').removeChild(document.getElementById('row2').lastElementChild);
}
#main {
width: 240px;
}
#row1 {
float: left;
}
#row2 {
float: right;
overflow: hidden;
}
.block1 {
display: block;
margin: 5px;
width: 100px;
height: 50px;
border: 1px solid red;
}
.block2 {
display: block;
margin: 5px;
width: 100px;
height: 100px;
border: 1px solid red;
}
<div id="main">
<div id="row1">
<span class="block1"></span>
<span class="block1"></span>
<span class="block1"></span>
<span class="block1"></span>
<span class="block1"></span>
</div>
<div id="row2">
<span class="block2"></span>
<span class="block2"></span>
<span class="block2"></span>
<span class="block2"></span>
<span class="block2"></span>
</div>
</div>
Hope this helps you, please tell me if there was something I didn't understand in your question to improve my answer.
I programmed it for you, this works after your existing JS code line:
var row2 = $('div.row2'),
block2elements = row2.children('span.block2');
// Function to use also for other situations
function calculateElementsHeight(elements) {
var height = 0;
$.each(elements, function(i, elementRaw ){
height += $(elementRaw).height();
})
return height;
}
for(var i = 0; block2elements.length > i; i++) {
block2elements = row2.children('span.block2'); // Get new state of the block2 elements
if(row2.height() < calculateElementsHeight(block2elements)) {
block2elements.last().remove();
}
}

Textarea scrollbar doesn't disappear despite width:0;

No scrollbar in Chrome, but in Firefox and IE, it doesn't disappear although the element width is 0:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Textarea Scrollbar</title>
<style>
textarea {
height: 200px;
width: 0;
margin: 0;
border: 0;
padding: 0;
}
</style>
</head>
<body>
<textarea id="textarea">
Hello, world!
...
</textarea>
<input type="text" value="0" id="input" oninput="resize();">
<script>
function resize() {
document.getElementById('textarea').style.width = document.getElementById('input').value + 'px';
}
</script>
</body>
</html>
DEMO
What's the reason? What's a cross-browser solution so the textarea behaves like in Chrome?
Try overflow:hidden; in the css for the text area attributed ID
EDIT
DEMO
Javascript
//this function attaches eventListener to the element and it is compatible with legacy browsers as well
function attach(element,listener,ev,tf){
if(element.attachEvent) {
//support for older IE (IE7 inclusive)
element.attachEvent("on"+listener,ev);
}else{
//modern browsers
element.addEventListener(listener,ev,tf);
}
}
var newInterval; //we use this to set interval and check say every 200 milliseconds
//whether the *height* of our *textarea* has changed and if it has
//than we set its *overflow* to *auto*, so the *scrollbar* will be
//visible, but when the *height* is *less or equal to 200* we set
//its *overflow* to *hidden*, so the *scrollbar* isn't visible!
var textarea = document.getElementById('textarea');
var input = document.getElementById('input');
//here we check if the value of our input element has changed than we change the width of our textarea
attach(input,'input',function(){
textarea.style.width = input.value + 'px';
},false);
//when our textarea recieves focus (is clicked on) we start running the interval and
//check every 200 milliseconds if the height of the textarea is equal or greater than
//200px we set its overflow to auto so the scrollbar becomes visible, and when the
//height is equal or less than 200px we set our textareas overflow to hidden, so no
//scrollbar is visible
attach(textarea,'focus',function(){
newInterval = setInterval(function(){
height = textarea.scrollHeight;
if(height>=200){
textarea.style.overflow = 'auto'
}else textarea.style.overflow = 'hidden';
},200);
},false);
//here we clear our interval, as our textarea has lost focus
// and there is no need to further run our interval
attach(textarea,'blur',function(){ clearInterval(newInterval); },false);
HTML
<textarea id="textarea">
Hello, world!
...
</textarea>
<input type="text" value="0" id="input" oninput="resize();">
CSS
textarea{
height: 200px;
width: 0;
margin: 0;
border: 0;
padding: 0;
overflow:hidden;
border:1px solid red;
}

Placeholder for contenteditable div

I have the following: FIDDLE
The placeholder works fine and dandy until you type something, ctrl + A, and delete. If you do that, the placeholder disappears and never shows up again.
What's wrong? How can I have a placeholder for a contenteditable div?
HTML:
<div class="test" placeholder="Type something..." contenteditable="true"></div>
CSS:
.test {
width: 500px;
height: 70px;
background: #f5f5f5;
border: 1px solid #ddd;
padding: 5px;
}
.test[placeholder]:empty:before {
content: attr(placeholder);
color: #555;
}
Thanks.
While searching for the same problem I worked out a simple mixed css-JavaScript solution I'd like to share:
CSS:
[placeholder]:empty::before {
content: attr(placeholder);
color: #555;
}
[placeholder]:empty:focus::before {
content: "";
}
JavaScript:
jQuery(function($){
$("[contenteditable]").focusout(function(){
var element = $(this);
if (!element.text().trim().length) {
element.empty();
}
});
});
Updated fiddle
from Placeholder in contenteditable - focus event issue
[contenteditable=true]:empty:not(:focus):before{
content:attr(data-ph);
color:grey;
font-style:italic;
}
I got this solution from: https://codepen.io/flesler/pen/AEIFc
Basically put this css code:
[contenteditable=true]:empty:before{
content: attr(placeholder);
pointer-events: none;
display: block; /* For Firefox */
}
And have the placeholder attribute in your contenteditable div.
I've created a live demo: "Placeholder for content-editable divs", by HTML & CSS.
Also, Codepen: https://codepen.io/fritx/pen/NZpbqW
Ref: https://github.com/fritx/vue-at/issues/39#issuecomment-504412421
.editor {
border: solid 1px gray;
width: 300px;
height: 100px;
padding: 6px;
overflow: scroll;
}
[contenteditable][placeholder]:empty:before {
content: attr(placeholder);
position: absolute;
color: gray;
background-color: transparent;
}
<textarea class="editor"
placeholder="Textarea placeholder..."
></textarea>
<br/>
<br/>
<div class="editor"
contenteditable
placeholder="Div placeholder..."
oninput="if(this.innerHTML.trim()==='<br>')this.innerHTML=''"
></div>
I see what you mean. In your fiddle I typed in a few characters and deleted it using 'ctrl-a' and 'delete', and the placeholder reappeared.
However, it seems as if when you hit 'enter' within the contenteditabele div it creates a child div containing the line break <div><br></div> creating an issue with the :empty pseudo-class which only targets elements with no child elements.**
Check it out in chrome developer tools or whatever you use.
From developer.mozilla.org
The :empty pseudo-class represents any element that has no children at all. Only element nodes and text (including whitespace) are considered. Comments or processing instructions do not affect whether an element is considered empty or not.
Ctrl-a will delete the text, but leaves the child div. Might be able to fix this by adding some javascript.
some fixes:
1) $element.text().trim().length - it solved problems with <div><br/></div> and
2) data-placeholder attr instead of placeholder - it is true way
3) common selector $("[contenteditable]") - it is true way
4) display: inline-block; - fix for Chrome and Firefox
JavaScript:
jQuery(function($){
$("[contenteditable]").blur(function(){
var $element = $(this);
if ($element.html().length && !$element.text().trim().length) {
$element.empty();
}
});
});
HTML:
<div data-placeholder="Type something..." contenteditable="true"></div>
CSS:
[contenteditable]:empty:before {
content: attr(data-placeholder);
color: grey;
display: inline-block;
}
It feels like I am repeating myself, but why not to check contenteditable element mutations? Trying to bind everything to event that are changing content are pain in the butt. What if You need to add button (For example paste), or change content dynamically (javascript). My approach would be using MutationObservers. Demo fiddle
HTML:
<div class="test" id="test" placeholder="Type something..." contenteditable="true"></div>
CSS:
.test {
width: 500px;
height: 70px;
background: #f5f5f5;
border: 1px solid #ddd;
padding: 5px;
}
.test[placeholder]:empty:before {
content: attr(placeholder);
color: #555;
}
JavaScript:
var target = document.querySelector('#test');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (target.textContent == '') {
target.innerHTML = '';
}
});
});
var config = { attributes: true, childList: true, characterData: true };
observer.observe(target, config);
Updating Christian Brink's answer, you could/should check for more events. You can do so by simply doing:
// More descriptive name
var $input = $(".placeholder");
function clearPlaceHolder() {
if ($input.text().length == 0) {
$input.empty();
}
}
// On each click
$input.keyup(clearPlaceHolder);
// Probably not needed, but just in case
$input.click(clearPlaceHolder);
// Copy/paste/cut events http://stackoverflow.com/q/17796731
$input.bind('input', (clearPlaceHolder));
// Other strange events (javascript modification of value?)
$input.change(clearPlaceHolder);
Finally, the updated JSFiddle
As swifft said, you can fix this with some super simple JS. Using jQuery:
var $input = $(".test");
$input.keyup(function () {
if ($input.text().length == 0) {
$input.empty();
}
});
On each keystroke it checks whether there's any input text present. If not, it whacks any child elements that may have been left behind by user interaction with the element -- e.g. the <div> swifft describes.
This solution worked for me. I'd converted this solution from angular to pure javaScript
In .html
<div placeholder="Write your message.." id="MyConteditableElement" onclick="clickedOnInput = true;" contenteditable class="form-control edit-box"></div>
In .css
.holder:before {
content: attr(placeholder);
color: lightgray;
display: block;
position:absolute;
font-family: "Campton", sans-serif;
}
in js.
clickedOnInput:boolean = false;
charactorCount:number = 0;
let charCount = document.getElementsByClassName('edit-box')[0];
if(charCount){
this.charactorCount = charCount.innerText.length;
}
if(charactorCount > 0 && clickedOnInput){
document.getElementById("MyConteditableElement").classList.add('holder');
}
if(charactorCount == 0 && !clickedOnInput){
document.getElementById("MyConteditableElement").classList.remove('holder');
}
getContent(innerText){
this.clickedOnInput = false;
}
I have this function, and I always use to prevent this kind of things.
I use my function in this way:
var notEmpty = {}
notEmpty.selector = ".no-empty-plz"
notEmpty.event = "focusout"
notEmpty.nonEmpty = "---"
neverEmpty(notEmpty)
And I just add the no-empty-plz to the Elements I that don't want to be empty.
/**
* Used to prevent a element have a empty content, made to be used
when we want to edit the content directly with the contenteditable=true
because when a element is completely empty, it disappears U_U
*
* #param selector
* #param event
* #param nonEmpty:
* String to be put instead empty
*/
function neverEmpty(params) {
var element = $(params.selector)
$(document).on(params.event, params.selector, function() {
var text = $(this).html()
text = hardTrim(text)
if ($.trim(text) == "") {
$(this).html(params.nonEmpty)
}
});
}
params is actually a json, so selector = params.selector as you can see
And hardTrim is also another fucntion I created is like a trim but includs &nbsp and <br/>, etc
function hardTrim(text) {
if (!exists(text)) {
return ""
}
text = text.replace(/^\&nbsp\;|<br?\>*/gi, "").replace(/\&nbsp\;|<br?\>$/gi, "").trim();
return text
}
This works for me and it's trim the long placeholder if the input is too small
[contenteditable="true"][placeholder]:empty:before {
content: attr(placeholder);
position: absolute;
left: 5px;
font-size: 13px;
color: #aaa;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
direction: ltr;
}
This happens because when you ctrl+A then delete, there is a <br> remaining in the innerHTML of the textarea. A simple jQuery/javascript solution can do the trick to empty out the textarea:
$(document).on('input','.test',function(){
if(this.innerHTML == '<br>'){
$(this).html('');
}
});
let contenteditableDiv = document.getElementById('contenteditableDiv');
contenteditableDiv.addEventListener('focus', function() {
let phs = this.querySelector('.placeholder-span');
if (phs != null) {
if (!this.hasOwnProperty('placeholderSpan')) {
this.placeholderSpan = phs;
}
phs.remove();
document.getSelection().setPosition(this, 0);
}
});
contenteditableDiv.addEventListener('focusout', function() {
if (this.textContent.trim().length == 0 && this.hasOwnProperty('placeholderSpan')) {
this.replaceChildren(this.placeholderSpan);
}
});
.placeholder-span {
opacity: 0.5;
}
<div id="contenteditableDiv" contenteditable="true"><span class="placeholder-span">Type something...</span></div>
And if You want to avoid contenteditable HTML formatting problems (leading/trailing spaces) and write it like a normal person:
<div id="contenteditableDiv" contenteditable="true">
<span class="placeholder-span">Type something...</span>
</div>
Then add:
window.addEventListener('load', function() {
let contenteditableDiv = document.getElementById('contenteditableDiv');
contenteditableDiv.innerHtml = contenteditableDiv.innerHtml.trim();
});
And if You want the placeholder to stay unitll there's input You need to put proper logic into mousedown, beforeinput and input event listeners.

Shrink DIV to text that's wrapped to its max-width?

Shrink wrapping a div to some text is pretty straightforward. But if the text wraps to a second line (or more) due to a max-width (as an example) then the size of the DIV does not shrink to the newly wrapped text. It is still expanded to the break point (the max-width value in this case), causing a fair amount of margin on the right side of the DIV. This is problematic when wanting to center this DIV so that the wrapped text appears centered. It will not because the DIV does not shrink to multiple lines of text that wrap. One solution is to use justified text, but that isn't always practical and the results can be hideous with large gaps between words.
I understand there's no solution to shrink the DIV to wrapped text in pure CSS. So my question is, how would one achieve this with Javascript?
This jsfiddle illustrates it: jsfiddle. The two words just barely wrap due to the max-width, yet the DIV does not then shrink to the newly wrapped text, leaving a nasty right-hand margin. I'd like to eliminate this and have the DIV resize to the wrapped text presumably using Javascript (since I don't believe a solution exists in pure CSS).
.shrunken {text-align: left; display: inline-block; font-size: 24px; background-color: #ddd; max-width: 130px;}
<div class="shrunken">Shrink Shrink</div>
It's not the prettiest solution but it should do the trick. The logic is to count the length of each word and use that to work out what the longest line is that will fit before being forced to wrap; then apply that width to the div. Fiddle here: http://jsfiddle.net/uS6cf/50/
Sample html...
<div class="wrapper">
<div class="shrunken">testing testing</div>
</div>
<div class="wrapper">
<div class="shrunken fixed">testing testing</div>
</div>
<div class="wrapper">
<div class="shrunken">testing</div>
</div>
<div class="wrapper">
<div class="shrunken fixed">testing</div>
</div>
<div class="wrapper">
<div class="shrunken" >testing 123 testing </div>
</div>
<div class="wrapper">
<div class="shrunken fixed" >testing 123 testing </div>
</div>
And the javacript (relying on jQuery)
$.fn.fixWidth = function () {
$(this).each(function () {
var el = $(this);
// This function gets the length of some text
// by adding a span to the container then getting it's length.
var getLength = function (txt) {
var span = new $("<span />");
if (txt == ' ')
span.html(' ');
else
span.text(txt);
el.append(span);
var len = span.width();
span.remove();
return len;
};
var words = el.text().split(' ');
var lengthOfSpace = getLength(' ');
var lengthOfLine = 0;
var maxElementWidth = el.width();
var maxLineLengthSoFar = 0;
for (var i = 0; i < words.length; i++) {
// Duplicate spaces will create empty entries.
if (words[i] == '')
continue;
// Get the length of the current word
var curWord = getLength(words[i]);
// Determine if adding this word to the current line will make it break
if ((lengthOfLine + (i == 0 ? 0 : lengthOfSpace) + curWord) > maxElementWidth) {
// If it will, see if the line we've built is the longest so far
if (lengthOfLine > maxLineLengthSoFar) {
maxLineLengthSoFar = lengthOfLine;
lengthOfLine = 0;
}
}
else // No break yet, keep building the line
lengthOfLine += (i == 0 ? 0 : lengthOfSpace) + curWord;
}
// If there are no line breaks maxLineLengthSoFar will be 0 still.
// In this case we don't actually need to set the width as the container
// will already be as small as possible.
if (maxLineLengthSoFar != 0)
el.css({ width: maxLineLengthSoFar + "px" });
});
};
$(function () {
$(".fixed").fixWidth();
});
I little late, but I think this CSS code can be useful for other users with the same problem:
div {
width: -moz-min-content;
width: -webkit-min-content;
width: min-content;
}
const range = document.createRange();
const p = document.getElementById('good');
const text = p.childNodes[0];
range.setStartBefore(text);
range.setEndAfter(text);
const clientRect = range.getBoundingClientRect();
p.style.width = `${clientRect.width}px`;
p {
max-width: 250px;
padding: 10px;
background-color: #eee;
border: 1px solid #aaa;
}
#bad {
background-color: #fbb;
}
<p id="bad">This box has a max width but also_too_much_padding.</p>
<p id="good">This box has a max width and the_right_amount_of_padding.</p>
I guess this is what you are thinking about, it can be done in css:
div {
border: black solid thin;
max-width: 100px;
overflow: auto;
}
You can see it here: http://jsfiddle.net/5epS4/
Try this:
https://jsfiddle.net/9snc5gfx/1/
.shrunken {
width: min-content;
word-break: normal;
}

Categories

Resources