I have been developing a chat room User Interface and I have a question.
In my UI I am using a div as my textbox and have a max message length of 500. There is a counter to show how many characters you have left, like '245/500'. On Google, this works fine but on safari, if you type one letter then delete it the counter goes from 1 to 4 and not 1 to 0. This also breaks my placeholder for the div.
Your help is much appreciated.
Here is some example code, clone it, then run it in Safari then try Google.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<title>HTML Stuff</title>
<style>
#text-div {
width: 200px;
padding: 10px;
max-height: 50px;
background-color: rgb(197, 226, 252);
overflow-y: scroll;
overflow-x: hidden;
}
#text-div:empty:before {
content: attr(data-placeholder);
color: rgb(102, 101, 101);
}
#counter {
font-weight: bolder;
font-size: 50px;
}
</style>
</head>
<body>
<div id="text-div" contenteditable="true" data-placeholder="This is a Placeholder"></div>
<div id="counter">0</div>
</body>
<script>
var text = document.getElementById('text-div')
var counter = document.getElementById('counter')
$('#text-div').on('input', () => {
counter.innerHTML = text.innerHTML.length
})
</script>
</html>
Using element.innerHTML will return the contents + the characters "&", "<", ">" as HTML entities "&", "<" and ">" - MDN
Use element.innerText.length to get consistent results about the length of ONLY the contents!
var text = document.getElementById('text-div')
var counter = document.getElementById('counter')
$('#text-div').on('input', () => {
counter.innerText = text.innerText.length - 1
})
Check the documentation!
Related
const faqData = [
{
id: 1,
question: "Who are we?",
answer:
"We enable upscaling careers through flexible, interactive and collaborative learning. We believe in building learning communities by bringing together mentors, young minds, and creators.",
}
];
let accordianBody = document.querySelector('h1');
function createFaq() {
// creating & adding all the FAQ's
let firstQuery = document.createElement('h3');
firstQuery.textContent = faqData[0].question;
accordianBody.appendChild(firstQuery).className = "faq";
// creating "+" sign to the FAQ header
let plusOne = document.createElement('span');
// adding plus sign to the FAQ header
plusOne.textContent = "+";
firstQuery.appendChild(plusOne).className = "show_btn";
// Adding addEventListener & displaying the answers.
plusOne.addEventListener('click', answerFirst);
function answerFirst() {
let ans1 = document.createElement('p');
ans1.textContent = faqData[0].answer;
plusOne.appendChild(ans1);
}
}
createFaq();
.faq {
width: 100%;
background-color: #4caf50;
border-radius: 8px;
margin: 0em 0em 1.4em;
padding: 0.7em 1.4em;
}
.faq .show_btn {
width: 24px;
height: 24px;
background-color: black;
outline: none;
box-shadow: none;
border: none;
color: white;
margin: 0em;
border-radius: 120px;
cursor: pointer;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Accordion</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="accordian_body">
<h1>MY FAQ's</h1>
</div>
<script id="faq-js" src="faq.js"></script>
</body>
</html>
I am new to Javascript development so please excuse me for the beginner-level question.
I want to show some text on the click of the button and hide it on the next click and keep toggling it as many times a user clicks it without changing my HTML.
Below is my code. I have achieved how to show the text but on the next click instead of hiding it the text is keep on adding.
const faqData = [
{
id: 1,
question: "Who are we?",
answer:
"We enable upscaling careers through flexible, interactive and collaborative learning. We believe in building learning communities by bringing together mentors, young minds, and creators.",
}
];
let accordianBody = document.querySelector('h1');
function createFaq() {
// creating & adding all the FAQ's
let firstQuery = document.createElement('h3');
firstQuery.textContent = faqData[0].question;
accordianBody.appendChild(firstQuery).className = "faq";
// creating "+" sign to the FAQ header
let plusOne = document.createElement('span');
// adding plus sign to the FAQ header
plusOne.textContent = "+";
firstQuery.appendChild(plusOne).className = "show_btn";
// Adding addEventListener & displaying the answers.
plusOne.addEventListener('click', answerFirst);
function answerFirst() {
let ans1 = document.createElement('p');
ans1.textContent = faqData[0].answer;
plusOne.appendChild(ans1);
}
}
createFaq();
const faqData = [
{
id: 1,
question: "Who are we?",
answer:
"We enable upscaling careers through flexible, interactive and collaborative learning. We believe in building learning communities by bringing together mentors, young minds, and creators.",
}
];
let accordianBody = document.querySelector('h1');
function createFaq() {
// creating & adding all the FAQ's
let firstQuery = document.createElement('h3');
firstQuery.textContent = faqData[0].question;
accordianBody.appendChild(firstQuery).className = "faq";
// creating "+" sign to the FAQ header
let plusOne = document.createElement('span');
// adding plus sign to the FAQ header
plusOne.textContent = "+";
firstQuery.appendChild(plusOne).className = "show_btn";
// Adding addEventListener & displaying the answers.
plusOne.addEventListener('click', answerFirst);
function answerFirst() {
let ans1 = document.createElement('p');
ans1.textContent = faqData[0].answer;
plusOne.appendChild(ans1);
}
}
createFaq();
.faq {
width: 100%;
background-color: #4caf50;
border-radius: 8px;
margin: 0em 0em 1.4em;
padding: 0.7em 1.4em;
}
.faq .show_btn {
width: 24px;
height: 24px;
background-color: black;
outline: none;
box-shadow: none;
border: none;
color: white;
margin: 0em;
border-radius: 120px;
cursor: pointer;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Accordion</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="accordian_body">
<h1>MY FAQ's</h1>
</div>
<script id="faq-js" src="faq.js"></script>
</body>
</html>
And below is my small HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Accordion</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="accordian_body">
<h1>MY FAQ's</h1>
</div>
<script id="faq-js" src="faq.js"></script>
</body>
</html>
Please help me out and thanks in advance.
What you did wrong?
you added the answer in a span instead of next after to the question. (create problem when you want to change the content of hide show button + to -)
You added the answer to the DOM (HTML) by dynamically.
you had put your question and answer in <h1> tag.
you added your firstAnswer() function in the createFaq() function.
In faqData[] there is no need for an id in each question because the index of array can be used as their ids.
What's the logic?
The accordion adds the question and answer to the DOM (HTML) at same time not when user click to show the answer. The difference is only that the answers are hidden by adding style display: none to the <p> element as default. And when user clicked on button to show the answer the class active with styling display: block is added to the element <p>. That's all about accordion.
I had just fixed "adding more answers instead of hiding it on a click". I don't recommend you use this code. But this one is for your understanding. So, this snippet had same logic for only one question.
const faqData = [{
id: 1,
question: "Who are we?",
answer: "We enable upscaling careers through flexible, interactive and collaborative learning. We believe in building learning communities by bringing together mentors, young minds, and creators."
}];
let accordianBody = document.querySelector('h1');
function createFaq() {
// creating & adding all the FAQ's
let firstQuery = document.createElement('h3');
firstQuery.textContent = faqData[0].question;
accordianBody.appendChild(firstQuery).className = "faq";
// creating "+" sign to the FAQ header
let plusOne = document.createElement('span');
// adding plus sign to the FAQ header
plusOne.textContent = "+";
firstQuery.appendChild(plusOne).className = "show_btn";
// Adding addEventListener & displaying the answers.
//plusOne.addEventListener('click', answerFirst);
//function answerFirst() {
let ans1 = document.createElement('p');
ans1.textContent = faqData[0].answer;
plusOne.appendChild(ans1);
//}
}
createFaq();
const showHideBtn = document.querySelector(".show_btn");
showHideBtn.addEventListener('click', showHideAnswer);
function showHideAnswer() {
const question = document.querySelector(".faq span.show_btn p");
question.classList.toggle("active");
}
.faq {
width: 100%;
background-color: #4caf50;
border-radius: 8px;
margin: 0em 0em 1.4em;
padding: 0.7em 1.4em;
}
.faq .show_btn {
width: 24px;
height: 24px;
background-color: black;
outline: none;
box-shadow: none;
border: none;
color: white;
margin: 0em;
border-radius: 120px;
cursor: pointer;
}
.show_btn p{
display: none;
}
.show_btn .active {
display: block;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Accordion</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="accordian_body">
<h1>MY FAQ's</h1>
</div>
<script id="faq-js" src="faq.js"></script>
</body>
</html>
For multiple FAQ
I rearranged the code by renaming the variables and fixing wrong append of the elements such as answer to the <span> tag. I recommend you use this code not above because it have almost all issues discussed above. In this code you just have to add questions and answers to the faqData[]
const faqData = [
{
question: "Who are we?",
answer: "We enable upscaling careers through flexible, interactive and collaborative learning. We believe in building learning communities by bringing together mentors, young minds, and creators."
},
{
question: "Lorem Ipsum?",
answer: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed elementum malesuada tortor, in pharetra nisl gravida at."
}
]
// We are not adding question and answer into this heading any more.
let accordianBody = document.querySelector('h1');
function createFaq(question, answer) {
// Edited - Renaming firstQuery to faqQuestion, ans1 to faqAnswer, plusOne to showHideBtn
// Edited - Now appending faqQuestion, and faqAnswer to faqContainer
let faqContainer = document.createElement('div');
faqContainer.className = "faq-container"
// creating & adding all the FAQ's
let faqQuestion = document.createElement('h3');
faqQuestion.textContent = question;
faqContainer.appendChild(faqQuestion).className = "faq";
// creating "+" sign to the FAQ header
let showHideBtn = document.createElement('span');
// adding plus sign to the FAQ header
showHideBtn.textContent = "+";
faqQuestion.appendChild(showHideBtn).className = "show_btn";
// Adding addEventListener & displaying the answers.
//plusOne.addEventListener('click', answerFirst);
//function answerFirst() {
let faqAnswer = document.createElement('p');
faqAnswer.textContent = answer;
faqContainer.appendChild(faqAnswer);
//}
// Edited - append faq question next after to the heading not inside it
let accordionBody = document.querySelector(".accordian_body");
accordionBody.appendChild(faqContainer);
}
for (let i = 0; i < faqData.length; i++) {
// Passing values to the function to display the FAQs
createFaq(faqData[i].question, faqData[i].answer)
}
// This one can also be done with forEach or for-of loop like that
// for (const iterator of faqData) {
// createFaq(faqData[i].question, faqData[i].answer)
// }
const showHideBtn = document.querySelectorAll(".faq-container .show_btn");
// This will ahow and hide the specific answer;
for (let i = 0; i < showHideBtn.length; i++) {
const btn = showHideBtn[i];
// You can also a seprate function like another but there is only two lines of code we want for this function
btn.addEventListener("click", function () {
let answer = document.querySelectorAll(".faq-container p");
// This will add/remove the class to hide/show the answer
answer[i].classList.toggle("active");
// This one is for changing the content of hide show button
if (answer[i].classList.contains("active")) {
btn.textContent = "-";
}
else {
btn.textContent = "+";
}
});
};
.faq-container {
width: 100%;
background-color: #4caf50;
border-radius: 8px;
margin: 0em 0em 1.4em;
padding: 0.7em 1.4em;
}
.faq-container .show_btn {
padding: 5px 10px;
width: 24px;
height: 24px;
background-color: black;
outline: none;
box-shadow: none;
border: none;
color: white;
margin: 0em;
border-radius: 120px;
cursor: pointer;
}
.faq-container p {
display: none;
}
.faq-container .active {
display: block;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Accordion</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="accordian_body">
<h1>MY FAQ's</h1>
</div>
<script id="faq-js" src="faq.js"></script>
</body>
</html>
I think you can add extra data of boolean type to event listener which changes on each click that will solve your issue.
Like:
onClick(if(b is true)
{let x = hide;
then b = false;
return;
}
else if(b is false){
let x = show;
then b =true;
return;
}
This way toggling will be ensured.
It works for me.
This is the task that I am stuck with:
Exercise 4
Here are some examples of colors as they are used in CSS:
Red: hsl (0, 100%, 50%)
Green: hsl (100, 100%, 50%)
Blue: hsl (250, 100%, 50%)
Note that only the first number changes between these tones.
You should use this to create round elements with random background colors. In addition, use what you have learned about positioning to place the elements in random positions when you click on the circle.
This is my code so far for the task:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
body{
height:100%;
}
div {
border-radius: 50%;
width: 100px;
height: 100px;
}
</style>
<script>
let bodyEl=document.querySelector("body");
let divEl=document.createElement("div");
divEl.style.backgroundColor=hsl(Math.random()*6);
bodyEl.appendChild(divEl);
</script>
</body>
</html>
1) backgroundColor needs a string value. You are passing value of type number
divEl.style.backgroundColor=hsl(Math.random()*6);
If you have opened the console, then you would have gotten the error as:
Uncaught ReferenceError: hsl is not defined
So you can use:
const hslValue = `hsl(
${getRandomValue(365)},
${getRandomValue(100)}%,
${getRandomValue(100)}%
)`
divEl.style.backgroundColor = hslValue;
let bodyEl = document.querySelector("body");
let divEl = document.createElement("div");
const getRandomValue = upto => Math.floor(Math.random() * (upto + 1));
const hslValue = `hsl(
${getRandomValue(365)},
${getRandomValue(100)}%,
${getRandomValue(100)}%
)`
divEl.style.backgroundColor = hslValue;
bodyEl.appendChild(divEl);
div {
border-radius: 50%;
width: 100px;
height: 100px;
}
Click to see the circle in action, enjoy :)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
body {
height: 100%;
}
div {
border-radius: 50%;
position: absolute;
width: 100px;
height: 100px;
}
</style>
<script>
let bodyEl = document.querySelector("body");
let divEl = document.createElement("div");
divEl.style.backgroundColor = `hsl(${(Math.random() * 100)}, 100%, 50%)`;
bodyEl.appendChild(divEl);
divEl.addEventListener('click', function() {
this.style.backgroundColor = `hsl(${(Math.random() * 100)}, 100%, 50%)`;
this.style.top = `${parseInt(Math.random() * 100)}px`
this.style.left = `${parseInt(Math.random() * 100)}px`
})
console.log(`hsl(${(Math.random() * 100)}, 100%, 50%)`);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3 Circle</title>
<style>
body {background: black;}
.container {display: flex;}
.circle {
width: 500px;
height: 500px;
-webkit-border-radius: 250px;
-moz-border-radius: 250px;
border-radius: 250px;
background: white;
}
.active {
background: yellow !important;
color: red;
}
</style>
</head>
<body>
<section class="container">
<button class="circle circle1">Circle1</button>
<button class="circle circle2">Circle2</button>
<button class="circle circle3">Circle3</button>
</section>
<script>
let cir1 = document.querySelector('.circle1')
let cir2 = document.querySelector('.circle2')
let cir3 = document.querySelector('.circle3')
let allCircle = document.querySelectorAll('.circle');
cir1.addEventListener('onClick', onButton1Click);
cir2.addEventListener('onClick', onButton2Click);
cir3.addEventListener('onClick', onButton3Click);
function onButton1Click() {
if (cir1.classList.contains("active")) {
allCircle.classList.remove('active');
} else {
allCircle.classList.remove('active');
cir1.classList.add('active');
}
}
function onButton2Click() {
if (cir2.classList.contains("active")) {
allCircle.classList.remove('active');
} else {
allCircle.classList.remove('active');
cir2.classList.add('active');
}
}
function onButton3Click() {
if (cir3.classList.contains("active")) {
allCircle.classList.remove('active');
} else {
allCircle.classList.remove('active');
cir3.classList.add('active');
}
}
</script>
</body>
</html>
I am trying to make 3 light bulbs represented by circles using HTML & CSS.
So if I turn one light bulb on using the button, the other ones should turn off using the addeventlistener. I can't find ways to make the light bulb turn yellow. Is there anything I am doing wrong? I looked for typos but I can't find any.
A few small things need to changed here.
The event type to be passed to the addEventListener is 'click' rather than 'onClick'.
The variable allCircle returns a list of dom nodes and not a single dom node. So it is essentially a []. Hence properties and methods that are available on a dom node are not accessible on the variable. What you can rather do is write a loop to access each element of the array and then modify their classes one by one
Might also suggest you to put debugger inside your code to see what is happening line by line. This article by Google should help you on using the Chrome dev tools.
This is my first answer on Stack Overflow.
let cir1 = document.querySelector('.circle1')
let cir2 = document.querySelector('.circle2')
let cir3 = document.querySelector('.circle3')
cir1.addEventListener('click', onButton1Click);
cir2.addEventListener('click', onButton2Click);
cir3.addEventListener('click', onButton3Click);
function removeActive() {
cir1.classList.remove('active');
cir2.classList.remove('active');
cir3.classList.remove('active');
}
function onButton1Click() {
removeActive();
cir1.classList.add('active');
}
function onButton2Click() {
removeActive();
cir2.classList.add('active');
}
function onButton3Click() {
removeActive();
cir3.classList.add('active');
}
body {
background: black;
}
.container {
display: flex;
align-items: flex-start;
}
.circle {
width: 100px;
height: 100px;
max-height: 100px;
-webkit-border-radius: 250px;
-moz-border-radius: 250px;
border-radius: 250px;
background: white;
}
.active {
background: yellow !important;
color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3 Circle</title>
</head>
<body>
<section class="container">
<button class="circle circle1">Circle1</button>
<button class="circle circle2">Circle2</button>
<button class="circle circle3">Circle3</button>
</section>
</body>
</html>
There seem to be two issues here.
When adding an event listener for a click event, it must be called with click that is to be passed as the first parameter to the listener, but you've added onClick
querySelectorAll returns a HTMLCollection. So classList will not be a valid property on it. You might want to loop through the elements from allCircles to remove the class.
I've modified the listener and corrected the classist related fix for the first button here https://jsfiddle.net/gr33nw1zard/y7f5wnda/
should be click event, not 'onClick'.
cir1.addEventListener('click', onButton1Click);
Created one common function for all 3 buttons. onClick event is not available in plain javascript, it's the click that is the correct keyword here. Also, you have to iterate over allCircle's object or use getElementsByClass. This will work for you!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3 Circle</title>
<style>
body {
background: black;
}
.container {
display: flex;
}
.circle {
width: 500px;
height: 500px;
-webkit-border-radius: 250px;
-moz-border-radius: 250px;
border-radius: 250px;
background: white;
}
.active {
background: yellow !important;
color: red;
}
</style>
</head>
<body>
<section class="container">
<button class="circle circle1">Circle1</button>
<button class="circle circle2">Circle2</button>
<button class="circle circle3">Circle3</button>
</section>
<script>
let cir1 = document.querySelector('.circle1')
let cir2 = document.querySelector('.circle2')
let cir3 = document.querySelector('.circle3')
let allCircle = document.querySelectorAll('.circle');
cir1.addEventListener('click', onButtonClick);
cir2.addEventListener('click', onButtonClick);
cir3.addEventListener('click', onButtonClick);
function onButtonClick(e) {
const cir = e.toElement;
if (cir.classList.contains("active")) {
Object.keys(allCircle).map(circle => allCircle[circle].classList.remove('active'));
} else {
Object.keys(allCircle).map(circle => allCircle[circle].classList.remove('active'));
cir.classList.add('active');
}
}
</script>
</body>
</html>
The onClick should be edited to click
I use the jQuery Tooltip Widget to show tooltips with very large contents. If the content is larger than the size of the tooltip window only the end of the content is shown.
I'm looking for a way to force the tooltip widget to show the beginning of the content (instead of the end of the content)
https://api.jqueryui.com/tooltip/
Example:
.mytooltip {
width: 300px;
border-top: solid 1px #BBBBBB;
border-bottom: solid 1px #444444;
padding: 5px 20px;
border-radius: 0;
box-shadow: 0 0 4px grey;
margin: 2px;
font-stretch: condensed;
white-space: pre-line;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Tooltip - Default functionality</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( document ).tooltip({
tooltipClass: "mytooltip",
});
} );
</script>
</head>
<body>
<p>This is a long website</p>
<p>This is a long website</p>
<p>This is a long website</p>
<p>This is a long website</p>
<p>Tooltip </p>
</body>
</html>
You'll need to use content option as a Function. Since you add "\r" as the Carriage Return, a very uncommon method, you can use this as a delimiter and split the text into an array. You can then slice off the portion you do not want showing up and join it back.
JavaScript
$(function() {
var toolTipMax = 10;
function chunk(s){
var dl = "\r";
if(s.indexOf("\r\n") > 1){
dl = "\r\n";
} else if(s.indexOf("\r") < 0 && s.indexOf("\n") > 1){
dl = "\n";
}
return s.split(dl);
}
$("body").tooltip({
tooltipClass: "mytooltip",
content: function(){
var c = $(this).attr("title");
var sArr = chunk(c);
var part = sArr.slice(0,toolTipMax);
return part.join("<br />");
}
});
});
Working Example: https://jsfiddle.net/Twisty/6h1oqn8s/
Consider that Windows uses CR and NL ("\r\n") as it's End Of Line (EOL) and Linux uses just NL ("\n") for it's EOL.
I'm building a programmable calculator that uses a contenteditable div as a place to enter the expression you need evaluated. The div listens for the user to strike the return key, then passes its text content to the calculator engine, clears itself, and then displays the result of the expression.
Everything works as expected on desktop browsers, but under mobile browsers, there is some strange behaviour when the return key is pressed when the caret is not at the end of the text. Sometimes hitting the return key will insert a space or newline, sometimes it will submit and clear everything before the caret, and under Chrome it seems to create a new div element. Any ideas what I'm doing wrong?
I have successfully tested the code in Firefox 59.0.2 and Chromium 65.0.3325.181 running on Ubuntu 17.10, and Firefox 59.0.2 running on Windows 7. I have unsuccessfully tested the code in Firefox 59.0.2 and Chrome 65.0.3325.109 running on Android 8.1.0.
window.addEventListener("load", function () {
"use strict";
var i = document.getElementById("i");
var o = document.getElementById("o");
i.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
e.preventDefault();
o.textContent = i.textContent;
i.textContent = "";
return false;
}
return true;
});
});
div {
font-size: 24px;
padding: 1em;
border: 1px solid black;
min-height: 1em;
box-sizing: content-box;
margin: 1em;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=0">
<title>Test</title>
</head>
<body>
<div id="i" contenteditable="true"></div>
<div id="o"></div>
</body>
</html>
EDIT:
After some work, I've made an alternate method of detecting input, copying the text, and clearing the box. It no longer listens for keystrokes but watches for changes in the box itself. It works on desktop but fails in similar ways on mobile. It also has the added problem that it will crash Firefox mobile if you type too quickly. I've tested the code on the same browsers as before.
window.addEventListener("load", function () {
"use strict";
var i = document.getElementById("i");
var o = document.getElementById("o");
var getText;
var input;
getText = function (e) {
var s = [];
e.childNodes.forEach(function (n) { // get every child
switch (n.nodeType) {
case 1: // element, so recurse
s.push(getText(n));
break;
case 3: // text
s.push(n.nodeValue);
break;
}
});
return s.join("");
};
input = function (e) {
if (i.childNodes.length > 1 || (i.firstChild && i.firstChild.nodeType != 3)) { // checking for added <br />
e.preventDefault();
o.textContent = getText(i);
while (i.firstChild) { // clear element
i.removeChild(i.firstChild);
}
// add and remove listener to prevent event from firing on clear
i.removeEventListener("input", input);
setTimeout(function () {
i.addEventListener("input", input, true);
}, 100);
return false;
}
return true;
};
i.addEventListener("input", input, true);
});
div {
font-size: 24px;
padding: 1em;
border: 1px solid black;
min-height: 1em;
box-sizing: content-box;
margin: 1em;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=0">
<title>Test</title>
</head>
<body>
<div id="i" contenteditable="true"></div>
<div id="o"></div>
</body>
</html>
EDIT 2 (with workaround):
After some thinking, I realized I don't need to use a contenteditable div, so I've re-worked it to use a styled text input field which works properly.
window.addEventListener("load", function () {
"use strict";
var f = document.getElementById("f");
var i = document.getElementById("i");
var o = document.getElementById("o");
f.addEventListener("submit", function (e) {
e.preventDefault();
o.textContent = i.value;
i.value = "";
return false;
});
});
input[type="text"] {
display: block;
width: 100%;
overflow: auto;
outline: none;
border: none;
border-radius: 0;
box-shadow: none;
box-sizing: content-box;
}
#i, div {
width: calc(100% - 4em - 2px);
min-height: 1em;
font-size: 24px;
padding: 1em;
border: 1px solid black;
min-height: 1em;
margin: 1em;
color: black;
font-family: serif;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=0">
<title>Test</title>
</head>
<body>
<form id="f" name="f">
<input id="i" name="i" type="text" >
</form>
<div id="o"></div>
</body>
</html>