I'm creating a simple app in which user provides a number and square is generated with the given number. The problem is that when the user provides the input the first time, it works as intended, but when the user edits the value and clicks the button, the value adds with the existing value and it displays the square of the sums. I want to reset the input after the button click, but haven't got any clue how to do. Any solutions
The html code looks like this
<div class="intro">
<h1>Select no of squares to be made</h1>
<small>This experience is better with the values between 400 and 600</small>
<input type="number" id="squaresInput">
<button class="show">Go</button>
</div>
And js file is
const userInput = document.getElementById('squaresInput')
const btnInput = document.querySelector('.show')
btnInput.addEventListener('click', () => getUserInput())
let squaresNum
function getUserInput(){
let squaresNum = userInput.value
for(let i = 0; i < squaresNum; i++){
const square = document.createElement('div')
square.classList.add('square')
square.addEventListener('mouseover', () => setColor(square))
square.addEventListener('mouseout', () => removeColor(square))
container.appendChild(square)
}
}
This should work for you :
userInput.value = ''
I'd start moving
const userInput = document.getElementById('squaresInput')
into "getUserInput()" function.
Then removing the "let squaresNum" outside the function, it is useless.
Gianluca
You can just add userInput.value = ""; at the end of function, which will clear out any existing value in the input.
const userInput = document.getElementById('squaresInput')
const btnInput = document.querySelector('.show')
btnInput.addEventListener('click', () => getUserInput())
let squaresNum;
function getUserInput(){
let squaresNum = userInput.value
for(let i = 0; i < squaresNum; i++){
const square = document.createElement('div')
square.classList.add('square')
square.addEventListener('mouseover', () => setColor(square))
square.addEventListener('mouseout', () => removeColor(square))
container.appendChild(square)
}
userInput.value = "";
}
<div class="intro">
<h1>Select no of squares to be made</h1>
<small>This experience is better with the values between 400 and 600</small>
<input type="number" id="squaresInput">
<button class="show">Go</button>
<div class="container"></div>
</div>
You need something like this before adding more squares.
container.innerHTML = ""
at the beginning of
function getUserInput () {....
In this way you will eliminate the squares previously created before creating the new ones.
Take advantage of using <form>.
Change <div class='intro'> to <form id='intro'>
Whatever .container is change it to <fieldset id='container'> or name='container'. If you don't want the border, in CSS .container {border:0;}
Now you just register the 'submit' event to the <form>
When any <button>, <input type='submit'>, or <button type='submit'> is clicked or a form control has focus and user keys Enter/Return the 'submit' event is triggered.
event.preventDefault() is to stop the <form> from trying to send data to a server.
this.reset(); will clear the <input>
Unless setColor() and removeColor() do more than just change div.square color, it's better just to use CSS. .square {border-color: red; background: red}
Using a <form> allows you to use the HTMLFormControlsCollection and HTML Forms API. It's terse and specific which allows more control and less coding.
// Reference a `<form>`
const f = document.forms['id'] /*OR*/ document.forms.id // name can be used as well
// Reference all form controls within <form>
const io = f.elements;
// Reference a form control within <form>
const btn = io['id'] /*OR*/ io.id // name can be used as well
Use createDocumentFragment(); because 400 to 600 nodes to render is really too much.
Also added a <button> to clear out the squares whenever the user wishes to.
Added 'mouseover/out' event handler. It's registered to the parent element only and not to each .square. This and the 'submit' event are possible because of Event Bubbling and how Event Delegation leverages it.
In the event handler hoverGrid() values, expressions, statements, and functions (ex. addColor() and removeColor()) can be placed within the appropriate case of the switch().
...
case 'mouseover':
addColor();
break;
case 'mouseout':
removeColor();
break;
...
const I = document.forms.intro;
const io = I.elements;
const clr = io.clr;
const box = io.box;
I.onsubmit = createGrid;
clr.onclick = clearGrid;
box.onmouseover = hoverGrid;
box.onmouseout = hoverGrid;
function createGrid(event) {
event.preventDefault();
let cnt = parseInt(io.qty.value);
let frag = document.createDocumentFragment();
for (let i = 0; i < cnt; i++) {
let square = document.createElement('div');
square.className = 'square';
square.dataset.index = i + 1;
frag.appendChild(square);
}
box.appendChild(frag);
this.reset();
console.clear();
console.log(box.childElementCount);
};
function clearGrid(event) {
this.previousElementSibling.innerHTML = '';
};
function hoverGrid(event) {
let E = event.type;
const sqr = event.target;
if (sqr.matches('.square')) {
switch (E) {
case 'mouseover':
console.log(sqr.dataset.index);
break;
case 'mouseout':
console.clear();
break;
default:
break;
}
}
};
:root {
font-size: 16px;
}
body {
font-family: Consolas;
line-height: 1;
overflow: auto;
}
input,
button {
display: inline-block;
font: inherit
}
small {
display: block;
margin-bottom: 4px;
}
button {
width: 4ch;
padding: 2px 5px;
cursor: pointer;
}
#box {
display: flex;
flex-flow: row wrap;
max-width: 96vw;
height: max-content;
}
.square {
width: 1ch;
height: 1ch;
border: 1px solid black
}
.square:hover {
border-color: red;
background: red;
}
#clr {
width: 9ch;
float: right;
}
/* SO Console Display - Rightside Column */
.as-console-wrapper {
width: 20% !important;
font-variant: normal;
font-weight: bold;
color: red;
}
.as-console-row.as-console-row::after {
content: '';
padding: 0;
margin: 0;
border: 0;
width: 0;
}
<form id="intro">
<fieldset>
<legend>Select no. of squares to be made</legend>
<small>This experience is better with the values between 400 and 600</small>
<input type="number" id="qty" min='0' max='1000'>
<button>GO</button>
</fieldset>
<fieldset id='box'></fieldset>
<button id='clr' type='button'>CLEAR</button>
</form>
Related
I have to following Code: JSFiddle – I have an HTML-input and a button. – When clicking on the button a function is being run, that changes the input.value to this text: "Black Yellow".
I would like the word Yellow to be yellow. color: yellow
I found some stuff on stack overflow, but nothing that really worked for me and from what I've read, I think this might be a bit tricky, but I really need this to work, so I would very much welcome any sort of input! – Simon
HTML
<button onclick="myFunction()"> Run </button>
<input name="input1" id="myInput">
JS
function myFunction() {
document.querySelector("#myInput").value = "Black Yellow";
}
It is not an easy task as someone else mentioned. I have done something similar in react but that is completely different from doing it in plain javascript.
Here is how you can create your own input field:
Javascript:
const input = {
element: document.getElementById("input"),
focusing: false,
value: ""
}
function checkTarget(e) {
if (e.target !== input.element){
input.focusing = false
return
}
input.focusing = true
console.log(input.focusing)
}
function handleKey(e) {
if (e.isComposing || e.keyCode === 229 || !input.focusing) {
return;
}
input.value += e.key
input.element.innerText = input.value
}
function handleClick() {
let texts = input.value.split(" ")
let span1 = document.createElement("span")
let span2 = document.createElement("span")
span1.innerText = texts[0] + " "
span2.innerText = texts[1]
span2.style.color = "yellow"
input.element.innerText = ""
input.element.appendChild(span1)
input.element.appendChild(span2)
}
document.addEventListener("click", checkTarget)
document.addEventListener("keydown", handleKey);
HTML:
<button id="button"onclick="handleClick()"> Run </button>
<div id="input">
</div>
CSS:
#input {
width: 150px;
height: 20px;
border: #777 1px solid;
border-radius: 2px;
display: inline-block;
}
Run this in your fillde and let me know if it works.
Of course, this will work if there are only 2 words present in the "input" field. For more words you will need to tweak this code.
What i had to do in react was - create a text area which shows each word in different color (rainbow input). So i created an array of all rainbow colors, created a rand function which chooses one of them and then I did a similar thing as i did here. I used map on the array i got from split(" ") and then returned a span with inline styling for the text color. And finally rendered the entire junk in the div. That was the easy part. The editing was the hard part (handling delete, backspace, tab etc). Which is when I gave up and stole a component off of github.
This is not an easy task. You can't control the word color in HTML at the moment. You need to create your own input somehow using 2 input elements. For every color an input. But as I said, it can get complicated.
function renderFakeInput(input, colors) {
input.style.display = "none";
const fakeInput = document.createElement("div");
fakeInput.classList.add("fake-input");
fakeInput.addEventListener("click", () => renderRealInput(input, colors, fakeInput));
for (const color of colors) {
const word = document.createElement("div");
word.textContent = color;
word.style.color = color;
fakeInput.appendChild(word);
}
input.parentElement.appendChild(fakeInput);
}
function renderRealInput(input, colors, fakeInput) {
const onBlur = () => {
input.removeEventListener("blur", onBlur);
renderFakeInput(input, input.value.split(" "));
};
input.style.display = "";
input.value = colors.join(" ");
input.focus();
input.addEventListener("blur", onBlur);
fakeInput.remove();
}
const colorString = "Black Yellow";
const colors = colorString.split(" ");
const inputs = Array.from(document.getElementsByClassName("multi-color"));
for (const input of inputs) {
renderFakeInput(input, colors);
}
.fake-input {
display: flex;
flex-direction: row;
}
<div>
<input class="multi-color" />
</div>
<div>
<input class="multi-color" />
</div>
I am working on a simple calculator project
I am attempting to automate adding event listeners to the various numeric buttons (1-9).
The event listeners will listen to click events on the buttons leading to a change in the display section of the HTML (class = .display)
key value pairs being b1-b9 containing the various corresponding values.
I have come up with the below FOR EACH loop. For some reason it causes all numerical buttons to apply the number 9; which i believe is the cause of the for each loop.
I am unsure how to quite fix it. I have also come up with an alternative FOR loop that leads to another problem. pairs[Properties[i]].toString() returns undefined.
interestingly if i swap pairs[Properties[i]].toString() out to just i then the SAME issue occurs
Help really appreciated and thank you..
const pairs = {
b1: 1,
b2: 2,
b3: 3,
b4: 4,
b5: 5,
b6: 6,
b7: 7,
b8: 8,
b9: 9,
};
var Properties = Object.keys(pairs);
function loadButtons () {
for (var item in pairs) {
//for each key property in pairs
console.log(item);
let targetCell = document.querySelector("." + item.toString())
// querySelector only targets the FIRST element found
// in this case only 1 for that name
console.log(targetCell);
targetCell.addEventListener('click', () => {
// you want it to manipulate the display as and when clicked
var currentDisplay = document.querySelector(".display").innerHTML.toString();
newDisplay = currentDisplay + pairs[item].toString();
document.querySelector(".display").innerHTML = newDisplay;
})
// console.log(pairs[item]);
// // pairs[item] retrieves the value to that "key"
}
};
function alternative() {
var i;
var Properties = Object.keys(pairs);
for (i = 0; i < Properties.length; i++) {
let targetCell = document.querySelector("." + Properties[i].toString())
// querySelector only targets the FIRST element found
// in this case only 1 for that name
console.log(targetCell);
targetCell.addEventListener('click', () => {
// you want it to manipulate the display as and when clicked
var currentDisplay = document.querySelector(".display").innerHTML.toString();
newDisplay = currentDisplay + pairs[Properties[i]].toString();
document.querySelector(".display").innerHTML = newDisplay;
})
};
};
Expected should be clicking of 1 to add a string "1" to the current string of the calculator, so on .
function onClick(item, pairs) {
return () => {
// you want it to manipulate the display as and when clicked
var currentDisplay = document.querySelector(".display").innerHTML.toString();
var newDisplay = currentDisplay + pairs[item].toString();
document.querySelector(".display").innerHTML = newDisplay;
}
}
var Properties = Object.keys(pairs);
function loadButtons () {
for (var item in pairs) {
//for each key property in pairs
console.log(item);
let targetCell = document.querySelector("." + item.toString())
// querySelector only targets the FIRST element found
// in this case only 1 for that name
console.log(targetCell);
targetCell.addEventListener('click', onClick(item, pairs))
// console.log(pairs[item]);
// // pairs[item] retrieves the value to that "key"
}
};
You should use event delegation instead of looping over and attaching events to every button. Here's an example:
var keyboard = document.getElementById('keyboard');
var display = document.getElementById('display');
keyboard.addEventListener('click', function(ev) {
var val = ev.target.id;
if (ev.target.localName === 'button') {
display.innerText += val;
}
});
.calculator {
width: 300px;
background: whitesmoke;
}
#display {
height: 50px;
background: #d2d2d2;
border: 1px solid #9c9c9c;
margin: 10px auto 10px;
font-size: 20px;
line-height: 50px;
padding: 0 10px;
}
#keyboard {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
button {
font-size: 20px;
padding: 20px;
margin: 5px;
cursor: pointer;
}
<div class="calculator">
<div id="display" contenteditable="true" >
</div>
<div id="keyboard">
<button id="0">0</button>
<button id="1">1</button>
<button id="2">2</button>
<button id="3">3</button>
<button id="4">4</button>
<button id="5">5</button>
<button id="6">6</button>
<button id="7">7</button>
<button id="8">8</button>
<button id="9">9</button>
</div>
</div>
i will do it this way but that not the only one i guess and there could be better ways.
const BUTTONS_NAMESVALUES={
//-- sound awful when a loop can do that!
bt0:0,bt1:1,bt2:2,bt3:3,bt4:4,bt5:5,bt6:6,bt7:7,bt8:8,bt9:9
}
function checkButtonValue(bt){
if(BUTTONS_NAMESVALUES[bt.id] !=null && BUTTONS_NAMESVALUES[bt.id] !='undefined'){
return bt.innerHTML;
}return;
}
//a button may look like that
<button id="bt1">1</button>
//-- with listener:
document.getElementById('bt1').addEventListener('click', function(e){
let chk=checkButtonValue(this);
if(chk!=null && chk!='undefined' && chk!=''){
document.getElementById('calculatorScreen').innerHTML=''+document.getElementById('calculatorScreen').innerHTML+chk;
}
});
I hope that help. I just replace the class name '.display' who can easily be a source of error(because it's the name of a CSS property and anything is display in HTML+ using an id better in that case because it's a unique element and can't be mistaken, classes aren't) and is not very accurate(as i write a correct constante name who has some meaning instead of pairs who means really nothing ^^).
Neither i 've automated the code into a loop but that's the easy part who is ever in your script.
I have a toggle button that changes a bit of text. The problem I run into is if I have 2 words and I want to change the text of one it changes one but when I toggle it off style is removed from both spans instead of the span of the selected text.
How can I remove the span from the specific text selected and leave the span on the other text?
function headuppercase(e) {
tags('span', 'sC');
}
function tags(tag, clas) {
var ele = document.createElement(tag);
ele.classList.add(clas);
wrap(ele);
}
function wrap(tags) {
var el = document.querySelector('span.sC');
sel = window.getSelection();
if (!el) {
if (sel.rangeCount && sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
range.surroundContents(tags);
} else {
var parent = el.parentNode;
while (el.firstChild) parent.insertBefore(el.firstChild, el);
parent.removeChild(el);
}
document.designMode = "off";
}
.ourbutton {
padding: 5px;
float: left;
font-variant: small-caps;
}
.container {
width: 200px;
height: 300px;
float: left;
}
.spanA {
width: 100px;
height: 80px;
max-width: 200px;
max-height: 300px;
float: left;
border: thin blue solid;
}
.sC {
font-variant: small-caps;
}
<button class="ourbutton" type="button" onclick="headuppercase();">Tt</button>
<div class="container">
<span class="spanA" contenteditable="true"></span>
</div>
No jQuery please. Thanks You!
The issue is with this document.querySelector('span.sC'). In all the case it will select the first span with sC which is not good as you have to deal with the current one.
Here is an idea of fix:
function headuppercase(e) {
tags('span', 'sC');
}
function tags(tag, clas) {
var ele = document.createElement(tag);
ele.classList.add(clas);
wrap(ele);
}
function wrap(tags) {
sel = window.getSelection();
if (sel.rangeCount && sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
range.surroundContents(tags);
if(tags.querySelector('.sC')) {
tags.classList.remove('sC');
tags.innerHTML=tags.querySelector('.sC').innerHTML;
}
document.designMode = "off";
}
.ourbutton {
padding: 5px;
float: left;
font-variant: small-caps;
}
.container {
width: 200px;
height: 300px;
float: left;
}
.spanA {
width: 100px;
height: 80px;
max-width: 200px;
max-height: 300px;
float: left;
border: thin blue solid;
}
.sC {
font-variant: small-caps;
}
<button class="ourbutton" type="button" onclick="headuppercase();">Tt</button>
<div class="container">
<span class="spanA" contenteditable="true"></span>
</div>
This is not so easily achievable without a library because of all the edge cases you need to handle. I will first touch on some edge cases and then give you an example on how to implement them.
Simple case
Say we have the following string inside a text Node
"Nobody wants too complex code because it becomes unmanageable."
Consider a user who selected the words "Nobody wants" and pressed the toggle small caps button. You should end up with something looking like this (where the bold segments represent text in small-caps):
"Nobody wants too complex code because it becomes unmanageable."
This is an easy case. Just wrap the segment "Nobody wants" inside a <span> and give it the sC class. The same goes for all other segments that are not yet in small caps. Nothing too hard here.
Edge case 1
But say you are in the following state (again, bold segments represent text in small-caps) :
"Nobody wants too complex code because it becomes unmanageable."
When the user selects and toggles the word "becomes" things become complicated. You have to:
Remove the last two words from their containing <span class="sC"> element
Add the word "becomes" after the span (from step 1.) and make sure it is not contained in a <span> that has the className sC.
Add the word "unmanageable" inside a new <span class="sC"> element after the text Node "becomes" (that was inserted in step 2.)
Edge case 2
Or say you are in the following state (again, bold segments represent text in small-caps) :
"Nobody wants too complex code because it becomes unmanageable."
What should happen when somebody selects and toggles the segment "wants too complex code because" ? One could say: make every character in this segment
small-caps : when it is not
back to normal : when it is currently small-caps
It's easy to see that you will again need a lot of splitting existing span elements, creating new text nodes, and so forth
Edge case N
Say you start with a nested list
A
B
C
D
and a user selects the two last items at once. Then you need to wrap each item separately into a span.
First step towards a solution
While it is not clear in your question how all the edge cases should be handled, here is a first step towards a solution.
const allCapsClass = 'sC'
function toggleCase() {
const selection = window.getSelection()
if (selection && selection.rangeCount>0) {
const spans = wrapInNonNestedSpanners(selection.getRangeAt(0))
for (const span of spans) {
const classes = span.classList
const action = classes.contains(allCapsClass) ? 'remove' : 'add'
classes[action](allCapsClass)
}
}
}
const spannerClassName = "non-nested-spanner"
const spannerQuerySelector = `.${spannerClassName}`
function wrapInNonNestedSpanners(range) {
const containingSpanner = getContainingSpanner(range)
const result = []
if (containingSpanner != null) { // Edge case 1
const endRange = document.createRange() // contents of the span after range
endRange.selectNode(containingSpanner)
endRange.setStart(range.endContainer, range.endOffset)
const endContents = endRange.cloneContents()
const wrappedSelectionContents = containingSpanner.cloneNode(false)
wrappedSelectionContents.appendChild(range.cloneContents())
endRange.deleteContents()
range.deleteContents()
const parent = containingSpanner.parentNode
const next = containingSpanner.nextSibling
parent.insertBefore(wrappedSelectionContents, next)
result.push(wrappedSelectionContents)
if (!isEmptySpanner(endContents.childNodes[0])) parent.insertBefore(endContents, next)
if (isEmptySpanner(containingSpanner)) parent.removeChild(containingSpanner)
const newSelection = document.createRange()
newSelection.selectNode(wrappedSelectionContents)
window.getSelection().removeAllRanges()
window.getSelection().addRange(newSelection)
} else { // Edge case 2
const contents = range.extractContents()
const spanners = contents.querySelectorAll(spannerQuerySelector)
let endRange = document.createRange() // range before the span
for (let index = spanners.length-1; index>=0; index--) {
const spanner = spanners[index]
endRange.selectNodeContents(contents)
endRange.setStartAfter(spanner)
if (!endRange.collapsed) {
const wrappedEndContents = createSpannerWrapping(endRange.extractContents())
range.insertNode(wrappedEndContents)
result.unshift(wrappedEndContents)
}
range.insertNode(spanner)
result.unshift(spanner)
}
const rest = createSpannerWrapping(contents)
if (!isEmptySpanner(rest)) {
range.insertNode(rest)
result.unshift(rest)
}
}
return result
}
function getContainingSpanner(range) {
let cursor = range.commonAncestorContainer
if (cursor.classList == undefined) cursor = cursor.parentElement
while (cursor.parentElement != null) {
if (cursor.classList.contains(spannerClassName)) return cursor
cursor = cursor.parentElement
}
return null
}
function createSpannerWrapping(childNode) {
const spanner = document.createElement('span')
spanner.classList.add(spannerClassName)
spanner.appendChild(childNode)
return spanner
}
function isEmptySpanner(spanner) {
if (spanner.childNodes.length == 0) return true
else if (spanner.childNodes.length == 1) {
const node = spanner.childNodes[0]
return node instanceof Text && node.length == 0
}
return false
}
.sC {
font-variant: small-caps;
}
<section contenteditable>
Hello world this is some text
</section>
<button onclick="toggleCase()">Toggle case</button>
This may be a stupid question that might be easy to find but i'm quite new to all of this and i can't seem to find what i'm looking for or atleast i don't know what i need to look for, thus I'm here.
So what I'm trying to do is create a kind of Linux terminal... This is what i got so far.
What I'm stuck on is the actual entering text part...
I've been trying to create a div with contenteditable=true as well as trying out Input elements but neither seems to be working how i want it to.
The current structure that i'm using for this is:
<div class="title" contenteditable="false" >
admin#localhost:~$
<div class="write-point" contenteditable="true" ></div>
<div class="linux-cursor" contenteditable="false"></div>
However this only deletes the whole line of text. "admin#localhost:~$" as well as the cursor.
I've also tried using JavaScript to put the cursor after the text but its not working at all.
function forStackOverFlow() {
var textInput = document.getElementsByClassName('write-point');
textInput.onkeydown = function(e) {
console.log(textInput.value);
var childTag = document.getElementsByClassName("write-point");
childTag.parentNode.insertBefore(textInput.value, childTag.nextSibling);
}};
So my main questions are:
How and what is needed to move a div(cursor element) to the end of input text(user input)
Is it possible to allow a user to type immediately once the webpage has loaded?
Thanks, any help would be great :)
You can do that in a better way with some CSS to make sure the "caret" element always comes after the contenteditable one and some JS to make sure the contenteditable element is always focused. You might try to do this last thing by adding autofocus to the contenteditable element and using a <label> for the caret element, but that doesn't work on contenteditable elements. Note no keyboard event listeners are needed:
const input = document.getElementById('input');
const caret = document.getElementById('caret');
// Move the focus back to the input if it moves away from it:
input.addEventListener('blur', (e) => {
input.focus();
});
// Set the focus to the input so that you can start typing straight away:
input.focus();
body {
background: #000;
color: #0F0;
font-family: monospace;
height: 100vh;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: scroll;
margin: 0;
padding: 16px;
}
#input {
display: inline;
word-break: break-all;
outline: none;
visibility: visible;
}
#caret {
border: 0;
padding: 0;
outline: none;
background-color: #0F0;
display: inline-block;
font-family: monospace;
}
admin#localhost:~$
<div id="input" contenteditable="true"></div><button id="caret" for="input"> </button>
In a more realistic example, you might want to:
Avoid trapping the focus in the contenteditable element, as that would prevent selecting previous commands. Instead, focus to the contenteditable element only once the user presses some key.
Show a different caret depending on its position: square if it's at the end of the input, line if it's somewhere else (unless overtype mode is enabled with the Ins key).
Add a new command/entry if ↵ is pressed.
Prevent entering formatted text and automatically split it up into multiple commands/entries when needed.
const history = document.getElementById('history');
const input = document.getElementById('input');
const cursor = document.getElementById('cursor');
function focusAndMoveCursorToTheEnd(e) {
input.focus();
const range = document.createRange();
const selection = window.getSelection();
const { childNodes } = input;
const lastChildNode = childNodes && childNodes.length - 1;
range.selectNodeContents(lastChildNode === -1 ? input : childNodes[lastChildNode]);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
function handleCommand(command) {
const line = document.createElement('DIV');
line.textContent = `admin#localhost:~$ ${ command }`;
history.appendChild(line);
}
// Every time the selection changes, add or remove the .noCursor
// class to show or hide, respectively, the bug square cursor.
// Note this function could also be used to enforce showing always
// a big square cursor by always selecting 1 chracter from the current
// cursor position, unless it's already at the end, in which case the
// #cursor element should be displayed instead.
document.addEventListener('selectionchange', () => {
if (document.activeElement.id !== 'input') return;
const range = window.getSelection().getRangeAt(0);
const start = range.startOffset;
const end = range.endOffset;
const length = input.textContent.length;
if (end < length) {
input.classList.add('noCaret');
} else {
input.classList.remove('noCaret');
}
});
input.addEventListener('input', () => {
// If we paste HTML, format it as plain text and break it up
// input individual lines/commands:
if (input.childElementCount > 0) {
const lines = input.innerText.replace(/\n$/, '').split('\n');
const lastLine = lines[lines.length - 1];
for (let i = 0; i <= lines.length - 2; ++i) {
handleCommand(lines[i]);
}
input.textContent = lastLine;
focusAndMoveCursorToTheEnd();
}
// If we delete everything, display the square caret again:
if (input.innerText.length === 0) {
input.classList.remove('noCaret');
}
});
document.addEventListener('keydown', (e) => {
// If some key is pressed outside the input, focus it and move the cursor
// to the end:
if (e.target !== input) focusAndMoveCursorToTheEnd();
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleCommand(input.textContent);
input.textContent = '';
focusAndMoveCursorToTheEnd();
}
});
// Set the focus to the input so that you can start typing straigh away:
input.focus();
body {
background: #000;
color: #0F0;
font-family: monospace;
height: 100vh;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: scroll;
word-break: break-all;
margin: 0;
padding: 16px;
}
#input {
display: inline;
outline: none;
visibility: visible;
}
/*
If you press the Insert key, the vertical line caret will automatically
be replaced by a one-character selection.
*/
#input::selection {
color: #000;
background: #0F0;
}
#input:empty::before {
content: ' ';
}
#keyframes blink {
to {
visibility: hidden;
}
}
#input:focus + #caret {
animation: blink 1s steps(5, start) infinite;
}
#input.noCaret + #caret {
visibility: hidden;
}
#caret {
border: 0;
padding: 0;
outline: none;
background-color: #0F0;
display: inline-block;
font-family: monospace;
}
<div id="history"></div>
admin#localhost:~$
<div id="input" contenteditable="true"></div><button id="caret" for="input"> </button>
In general, it's usually a bad idea to listen for keyboard events (keydown / keypress / keyup) to handle text input or cursors, as the value of the input can also be updated by pasting or dropping text into it and there are many edge cases, such as arrows, delete, escape, shortcuts such as select all, copy, paste... so trying to come up with an exhaustive list of all the keys we should take care of is probably not the best approach.
Moreover, that won't work on mobile, where most keys emit the same values e.key = 'Unidentified', e.which== 229 and e.keyCode = 229.
Instead, it's usually better to rely on other events such as input and use KeyboardEvents to handle very specific keys, like ↵ in this case.
If you need to check KeyboardEvent's properties values such as e.key, e.code, e.which or e.keyCode you can use https://keyjs.dev. I will add information about these kinds of cross-browser incompatibilities soon!
Disclaimer: I'm the author.
I suggest to use span instead of div because it's an inline element, more easy to manage in your case.
Next you can catch eatch keyboard's entry with a listener :
document.body.onkeydown
and tell him to append each key into a variable that you can display.
I let you think about all the functionnality you have to manage, like an enter or a backspace event.
you can play and find code you will need here : http://keycode.info/
Here is a working snippet :
var command = "";
document.body.onkeydown = function(e){
var writePoint = document.getElementById('writePoint');
command += String.fromCharCode(e.keyCode);
writePoint.innerHTML = command;
};
.linux-cursor{
width:7px;
height:15px;
background-color:green;
display:inline-block;
}
<span class="title" contenteditable="false" >
admin#localhost:~$
</span>
<span class="write-point" id="writePoint" contenteditable="true" ></span>
<span class="linux-cursor" contenteditable="false"></span>
Hope this help you, it looks like a nice project.
I've created a sample file that has adding and removing children elements in a mother element. The adding was successful, but I'm having a problem in removing children elements. The goal is every single click of remove button removes a child element.
var adds = document.querySelector('#add');
var remove = document.querySelector('#remove');
var section = document.querySelector('section');
var div;
adds.onclick = function() {
div = document.createElement('div');
section.appendChild(div);
div.classList.add('style');
};
// THE PROBLEM
remove.onclick = function() {
while(section.children.length !== 0) {
for(var c in section.children) {
section.removeChild(div[c]);
}
}
};
section {
display: flex;
background: #0ff;
height: 100px;
padding: 8px;
}
section > div {margin: 0 1px;}
.style {
width: 100px;
height: 100px;
background: lightgreen;
}
<button id="add">Add</button>
<button id="remove">Remove</button>
<section class="container"></section>
<br>
What's wrong with my code?
What's wrong with my code?
This line
section.removeChild(div[c]);
div could be undefined here and anyways, sections's children are not necessarily div's children
so change it to
section.removeChild(section.children [c]);
Also, while is not necessary
remove.onclick = function() {
for(var c in section.children) {
section.removeChild(section.children[c]);
}
};
EDIT
The goal is every single click of remove button removes a child
element.
remove.onclick = function() {
if(section.children length ) {
section.removeChild(section.children[0]);
}
};
changed for with if