How to change html element after text has been edited - javascript

I have this code that should change the contents of a table position when you change the contents of nameDiv which is var nameDiv = document.createElement('div'); (nameDiv.contentEditable = "true";) and it has some text
nameDiv.onclick = event => {
allSectionsArray[sectionNumber][2] = event.value;
}
What happens here is basically: it updates the second I click on the div and not after some text is entered.
What I want is: to save changes after entering the text. Is there any substitute of onclick or any other method that I can achieve this?

For contenteditable, you can use the input event:
The input event fires when the value of an input element has been changed.
The change event isn't supported for contenteditable, but you can use the blur event instead which
fires when an element has lost focus
const example = document.querySelector("#example");
example.addEventListener("blur", ()=>{
console.log(`Value is: ${example.textContent}`);
});
#example {
border: solid 1px #000;
padding: 5px;
}
<div id="example" contenteditable></div>
You can also browse the full list of events here.

Find below a sample how different events work with contenteditable elements.
You will probably need blur event handler to get the value when user is done typing.
const code = document.querySelector('pre');
const object = {
valueOnclick: '',
valueOninput: '',
valueOnblur: ''
};
document.querySelector('div').addEventListener('input', e => {
object.valueOninput = e.target.textContent;
code.innerHTML = JSON.stringify(object, null, 4);
});
document.querySelector('div').addEventListener('click', e => {
object.valueOnclick = e.target.textContent;
code.innerHTML = JSON.stringify(object, null, 4);
});
document.querySelector('div').addEventListener('blur', e => {
object.valueOnblur = e.target.textContent;
code.innerHTML = JSON.stringify(object, null, 4);
});
<div contenteditable>Some content</div>
<pre></pre>

Related

Button Functionality in JS

I created A simple Website for basic add/remove friend.
But the button is only working in first box and not working in other two boxes.
Button is completly working in first box but second and button button is not responding
Edit - All the buttons only changing the first box text
var arr = [{
name: "Raju",
image: "./1.jpg",
status: "Stranger"
},
{
name: "Shyam",
image: "./2.jpg",
status: "Stranger"
},
{
name: "Babu Bhaiya",
image: "./3.jpg",
status: "Stranger"
},
];
var cluster = "";
arr.forEach(function(val) {
cluster = cluster + `
<div id="card">
<img src="${val.image}" alt="">
<h1>${val.name}</h1>
<p>${val.status}</p>
<button>Send Request</button>
</div>
`;
});
//Botton Functionality
document.getElementById("cluster").innerHTML = cluster;
const btn = document.querySelectorAll("button");
var fact = 0;
btn.forEach((btn) => {
btn.addEventListener("click", function() {
if (fact == 0) {
var timer = setTimeout(() => {
document.querySelector("p").textContent = "Friend";
document.querySelector("p").style.color = "rgb(66, 178, 113";
document.querySelector("button").textContent = "Cancel";
}, 2000);
fact = 1;
document.querySelector("p").textContent = "Request Pending";
document.querySelector("button").textContent = "Send Request";
} else {
clearTimeout(timer);
fact = 0;
document.querySelector("p").textContent = "Strenger";
document.querySelector("p").style.color = "rgb(221, 66, 66)";
document.querySelector("button").textContent = "Send Request";
fact = 0;
}
});
});
<div class="container">
<div id="cluster" class="box"></div>
</div>
Instead of attaching listeners to all of the buttons you can use event delegation and attach one listener to a containing element that can listen out for events from its children as they "bubble up" the DOM.
In this example when a button is clicked we destructure out its previousElementSibling (the paragraph element), and its textContent. And then based on the textContent perform the operations.
(I've also used a switch statement instead of flags which makes the code a little cleaner, and map to produce the HTML rather than forEach.)
const arr=[{name:"Raju",image:"./1.jpg",status:"Stranger"},{name:"Shyam",image:"./2.jpg",status:"Friend"},{name:"Babu Bhaiya",image:"./3.jpg",status:"Request Pending"}];
// `map` over the array and produce an array of
// strings which is `joined` up at the end of the iteration
const html = arr.map(obj => {
return `
<div id="card">
<img src="${obj.image}" alt="">
<h3>${obj.name}</h3>
<p>${obj.status}</p>
<button type="button">Send Request</button>
</div>
`;
}).join('');
// Cache the container/cluster elements
const cluster = document.querySelector('#cluster');
const container = document.querySelector('.container');
// Add the event listener to the container
container.addEventListener('click', handleClick);
// Add the HTML to the cluster element
cluster.innerHTML = html;
// When the listener catches an event...
function handleClick(e) {
// ...first check that it's a button...
if (e.target.matches('button')) {
// ...destructure the previous element sibling
// element (relabeling it as `para`), and the textContent
// from the clicked element
const {
previousElementSibling: para,
textContent
} = e.target;
// Depending on the text content value
// choose the path to take
switch (textContent) {
case 'Send Request': {
para.textContent = 'Request Pending';
e.target.disabled = true;
setTimeout(() => {
para.textContent = 'Friend';
para.className = 'friend';
e.target.textContent = 'Cancel';
e.target.disabled = false;
}, 2000);
break;
}
case 'Cancel': {
para.textContent = 'Strenger';
para.className = 'strenger';
e.target.textContent = 'Send Request'
break;
}
}
}
}
.friend { color: rgb(66, 178, 113); }
.strenger { color: rgb(221, 66, 66); }
<div class="container">
<div id="cluster" class="box"></div>
</div>
Additional documentation
Destructuring assignment
map
matches
You are using document.querySelector() which will only select the first matched item.
According to MDN
The Document method querySelector() returns the first Element within the document that matches the specified selector, or group of selectors.
Instead what you need is querySelectorAll
const buttons = userList.querySelectorAll("button");
buttons.forEach((btn) => {
btn.addEventListener("click", function() {
//...
}
});
Edit:
All buttons changing the first text is the same issue as querySelector. It can be fixed by scoping it.
const buttons = userList.querySelectorAll("button");
buttons.forEach((btn) => {
btn.addEventListener("click", function() {
btn.querySelector(":scope > p").textContent = "Friend";
// ...
}
});

Changing color of a certain word in a paragraph using js

$('#txtInput').keyup(function(){
var txtInput = $(this).val();
localStorage.setItem('inputData', txtInput);
var returnData = localStorage.getItem('inputData');
$('#txtInput').val(returnData);
var hasTest = returnData.includes("<h1>");
if(hasTest == true){
}
});
I'm creating a text editor using js.Here I'm using localStorage to store data and retrieve data.I need to add highlighting syntax feature.
For example : If 'h1' found from the text, color the 'h1' to red.I used ".includes() and it finds the word but I have no idea how to change the color of the found text.I really appreciate your help
Try this solution:
1. Use a contenteditable element instead of input or textarea.
<div id="txtInput" contenteditable></div>
The reason is we need to display HTML with CSS inside of this input area.
2. Filter the result with highlight text.
// get highlight text from string
const highlighten = (string, highlight) => {
string = stripHtml(string);
// add highlight
if (string.includes(highlight)) {
string = string.replaceAll(highlight, `<span style="color:red">${highlight}</span>`);
}
return string;
};
Use replaceAll to set the highlight CSS. You can see the stripHTML() which is for cleaning the string before doing the filter.
3. Handle keyup event
// on keyup event
$('#txtInput').on('keyup', function(e) {
const $input = $(e.currentTarget);
// you can manage your localStorage data here
// ...
// filter the input
$input.html(highlighten($input.html(), 'h1'));
// set caret
setCaretAtTheEnd($input.get());
});
As we replace the #txtInput with the new result, we will lose the caret position, that's why we need setCaretAtTheEnd() to set the caret back to the end of input.
// https://stackoverflow.com/questions/822452/strip-html-from-text-javascript
function stripHtml(html) {
let tmp = document.createElement("DIV");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
}
// https://stackoverflow.com/questions/6249095/how-to-set-the-caret-cursor-position-in-a-contenteditable-element-div
function setCaretAtTheEnd(el) {
var range = document.createRange()
var sel = window.getSelection()
const nodes = el[0].childNodes;
let offset = 0;
nodes.forEach((node, i) => {
offset = node.length || 1;
});
range.setStart(nodes[nodes.length - 1], offset)
range.collapse(true)
sel.removeAllRanges()
sel.addRange(range)
}
// get highlight text from string
const highlighten = (string, highlight) => {
string = stripHtml(string);
// add highlight
if (string.includes(highlight)) {
string = string.replaceAll(highlight, `<span style="color:red">${highlight}</span>`);
}
return string;
};
// on keyup event
$('#txtInput').on('keyup', function(e) {
const $input = $(e.currentTarget);
// you can manage your localStorage data here
// ...
// filter the input
$input.html(highlighten($input.html(), 'h1'));
// set caret
setCaretAtTheEnd($input.get());
});
#txtInput {
width: 400px;
height: 200px;
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="txtInput" contenteditable>
</div>
This should do it for you:
if (hasTest == true) {
returnData = returnData.replace('<h1>', '<h1 style="color:red">')
$('#txtInput').val(returnData);
}

How to make a certain part of textarea read-only

I have a text area and I dynamically add data to it and it works fine. What I wanted to achieve was after the data is appended that data cant be altered (edited) but after the last element of the data user can start typing on the textarea. I was thinking of maybe calculating the length of string data the set read-only to that part. How can I achieve this? Any help is appreciated. Thanks in advance.
For a visual example take a look at the terminal of this website: https://www.online-python.com/
function test() {
x = 'this is a string to be appended to text area'
document.getElementById("textArea").value = x;
}
<textarea id="textArea"></textarea>
<button onclick="test()">Append</button>
You can add a keydown event listener that checks whether the selectionStart is smaller than the length of the textarea's value minus the length of the string appended:
let x = 'this is a string to be appended to text area'
var hasAppended = false;
function test() {
hasAppended = true
document.getElementById("textArea").value = x;
}
textArea.addEventListener('keydown', function(e) {
if (hasAppended) {
if (this.selectionStart > this.value.length - x.length && this.selectionStart != this.value.length) {
e.preventDefault()
e.stopPropagation()
}
}
})
<textarea id="textArea"></textarea><button onclick="test()">Append</button>
It is not possible to selectively mark parts of a <textarea> read-only, however, a similar effect can be achieved with contenteditable elements:
function test() {
const x = 'this is a string to be appended to text area'
const span = document.createElement('span')
span.appendChild(document.createTextNode(x))
span.setAttribute('contenteditable', 'false')
document.getElementById("textArea").appendChild(span);
}
#textArea{
border: 1px solid black;
height: 50px;
}
<div id="textArea" contenteditable="true"></div>
<button onclick="test()">Append</button>
However, this will still allow the user to delete the read-only block, or write before it.
This almost works.
EDIT:
Improved it a bit by changing keydown to keyup.
Now there is no need for space at the end of the read-only text and CTRL+a and then backspace will make the text come back almost instantly.
Maybe you can improve on it.
window.onload = function() {
document.getElementById('textArea').addEventListener('keyup', function (e){
var readOnlyLength = parseInt(document.getElementById("textArea").getAttribute('data-readOnlyLength') );
var currentLength = parseInt(document.getElementById("textArea").value.length );
if (readOnlyLength >= currentLength ) {
document.getElementById("textArea").value = document.getElementById("textArea").getAttribute('data-readonly') ;
}
}, false);
};
function test() {
x = 'this is a string to be appended to text area'
document.getElementById("textArea").value = x;
document.getElementById("textArea").setAttribute('data-readonly' , x);
document.getElementById("textArea").setAttribute('data-readOnlyLength' , x.length);
}
<textarea id="textArea"></textarea>
<button onclick="test()">Append</button>

Keydown Event to save locally using javascript

I am making a basic online editing interface for coursework. I would like to save the content of my #textarea div every time there's a keydown event. The code works partially but I can't edit the text without the cursor going to the top of the div.
document.addEventListener('keydown', saveLocally);
const saveLocally = function() {
let areaText = document.getElementById("textArea");
console.log(areaText);
let text = document.getElementById("textArea").innerHTML;
console.log(text);
let localData;
localStorage.setItem('siteData', text);
localData = localStorage.getItem('siteData');
console.log(localData);
console.log(localStorage.getItem('siteData'));
areaText.innerHTML = localData;
}
<div id="inputArea">
<div id="textArea" contenteditable="true"></div>
</div>
The issue is because you immediately update the innerText of the element after someone types. This affects the cursor as the node contents are changed. You don't need to do this anyway, so the line can be removed.
You instead need to perform the logic which retrieves the value from localStorage when the page loads. Also note that you should use the keyup event instead of keydown, otherwise you'll not save the last key which was pressed. Try this:
<div id="inputArea">
<div id="textArea" contenteditable="true"></div>
</div>
let areaText = document.getElementById("textArea");
const saveLocally = function() {
let text = areaText.innerHTML;
localStorage.setItem('siteData', text);
}
const retrieveSavedText = function() {
var text = localStorage.getItem('siteData');
areaText.innerHTML = text;
}
document.addEventListener('keyup', saveLocally);
retrieveSavedText();
Working example

ContentEditable div - set cursor position after updating inner html

I have a spell check solution that uses a content editable div and inserts span tags around words that are misspelled. Every time the inner html of the div is updated, the cursor moves to the beginning of the div.
I know I can move the cursor to the end of the div if the user adds new words to the end of the sentence (code below).
Old Text: This is a spell checker|
New Text: This is a spell checker soluuution|
var range = document.createRange();
range.selectNodeContents(element[0]);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
However, I am unable to retain the cursor position if the user adds words in the middle of a sentence.
Old Text: This is a spell checker
New Text: This is a new spell checker|
In the above case, the cursor goes to the end of the div when it should be after "new".
How do I retain the cursor position? Since I am updating the html and adding nodes, saving the range before the update and adding it to the selection object isn't working.
Thanks in advance.
As far as I know, changing the content of the div will always have problem.
So here is the solution that I came with. Please type error word such as helloo, dudeee
This should ideally work for textarea as well.
Solution details:
Use a ghost div with same text content
Use transparent color for the ghost div
Use border-bottom for the ghost div span text
Change zIndex so that it does't appear infront
// some mock logic to identify spelling error
const errorWords = ["helloo", "dudeee"];
// Find words from string like ' Helloo world .. '
// Perhaps you could find a better library you that does this logic.
const getWords = (data) =>{
console.log("Input: ", data);
const allWords = data.split(/\b/);
console.log("Output: ", allWords)
return allWords;
}
// Simple mock logic to identify errors. Now works only for
// two words [ 'helloo', 'dudeee']
const containsSpellingError = word => {
const found = errorWords.indexOf(word) !== -1;
console.log("spell check:", word, found);
return found;
}
const processSpellCheck = text => {
const allWords = getWords(text);
console.log("Words in the string: ", allWords);
const newContent = allWords.map((word, index) => {
var text = word;
if(containsSpellingError(word.toLowerCase())) {
console.log("Error word found", word);
text = $("<span />")
.addClass("spell-error")
.text(word);
}
return text;
});
return newContent;
}
function initalizeSpellcheck(editorRef) {
var editorSize = editorRef.getBoundingClientRect();
var spellcheckContainer = $("<div />", {})
.addClass("spell-check")
.prop("spellcheck", "false");
var spellcheckSpan = $("<span />")
.addClass("spell-check-text-content")
.css({
width: editorSize.width,
height: editorSize.height,
position: "absolute",
zIndex: -1
});
var text = $(editorRef).text();
var newContent = processSpellCheck(text);
spellcheckSpan.append(newContent);
spellcheckContainer.append(spellcheckSpan);
spellcheckContainer.insertBefore(editorRef);
$(editorRef).on("input.spellcheck", function(event) {
var newText = $(event.target).text();
var newContent = processSpellCheck(newText);
$(".spell-check .spell-check-text-content").text("");
$(".spell-check .spell-check-text-content").append(newContent);
});
}
$(document).ready(function() {
var editor = document.querySelector("#editor");
initalizeSpellcheck(editor);
});
#editor {
border: 1px solid black;
height: 200px;
}
.spell-check {
color: transparent;
}
.spell-error {
border-bottom: 3px solid orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="editor" contenteditable="true" spellcheck="false">
dudeee
</div>
This answer might work from SitePoint:
Store the selection x, y:
cursorPos=document.selection.createRange().duplicate();
clickx = cursorPos.getBoundingClientRect().left;
clicky = cursorPos.getBoundingClientRect().top;
Restore the selection:
cursorPos = document.body.createTextRange();
cursorPos.moveToPoint(clickx, clicky);
cursorPos.select();
SitePoint Article: Saving/restoring caret position in a contentEditable div
Update 25.10.2019:
The solution mentioned above doesn't work anymore since functions are used that are deprecated. Does chrome supports document.selection?

Categories