I'm learning Javascript and I would like to understand something.
I tried to build HTML nodes using Javascript and my code works when I split instructions but not when I try to compress :
I have a tab with text :
var tabText = [
'The ',
'Moon',
];
This code works :
var s1 = document.createElement('strong');
s1.appendChild(document.createTextNode(tabText[1]));
div.appendChild(s1);
But this one doesn't :
div.appendChild(document.createElement('strong').appendChild(document.createTextNode(tabText[1])));
Could you give me some explanations ?
Thanks.
document.createElement('strong').appendChild(document.createTextNode(tabTexte[1]))
node.appendChild returns the appended child node , therefore the upper code will return the textNode(wich is then appended to to div wich makes the strong elem go to nowhere)...
You may want this:
div.appendChild(document.createElement('strong')).appendChild(document.createTextNode(tabText[1]));
The result of appendChild is the child, not the parent. In other words, your code is equivalent to
var strongNode = document.createElement('strong');
var textNode = document.createTextNode(tabText[1]));
strongNode.appendChild(textNode); // returns textNode
div.appendChild(textNode);
Therefore, strongNode will not be added to the document.
//Create Node
(function () {
function createTodoNode(todo) {
const node = document.createElement("li");
node.classList.add("todo-item");
node.innerHTML = `
<button class="done-state">
<div class="done-state-filler"></div>
</button>
<div class="todo-name"></div>
<button class="delete-todo">✖</button>
`;
node.querySelector(".todo-name").textContent = todo.value;
const doneButton = node.querySelector("button.done-state");
const deleteButton = node.querySelector("button.delete-todo");
node.setAttribute("data-completed", todo.isComplete);
doneButton.addEventListener("click", () => {
const currentlyCompleted = JSON.parse(
node.getAttribute("data-completed") || "false"
);
node.setAttribute("data-completed", !currentlyCompleted);
todo.isComplete = !currentlyCompleted;
todoService.updateTodo(todo);
});
deleteButton.addEventListener("click", () => {
node.parentNode.removeChild(node);
todoService.removeTodo(todo.id);
});
return node;
}
window.todoView = {
createTodoNode,
};
})();
// Second Variation
<button onclick="create()">Aufgabe erstellen</button>
<ul class="items">
Meine Aufgabe:
</ul>
function create(){
let list = document.querySelector(".items");
let node = document.createElement("li");
node.classList.add("todo-item");
node.style ="color: orangered";
node.innerHTML = '<div class= "todo-item"></div>';
list.appendChild(node);
node.querySelector(".todo-item").textContent = "Eine erste Aufgabe";
console.log(node);
}
// Third Variation
(function() {
function createTaskNode(param) {
let categorie = document.querySelector("#" + param.key);
let newTaskDiv = document.createElement("div");
newTaskDiv.classList.add("task");
newTaskDiv.setAttribute("id", param.id);
newTaskDiv.setAttribute("data-assigned", param.isComplete);
if (newTaskDiv.getAttribute("data-assigned") == "undefined") {
newTaskDiv.setAttribute("data-assigned", false);
}
newTaskDiv.innerHTML = `
<div class="task-name">
<div class="task-value"></div>
<div class="data-assigned-button"></div>
</div>
<div class="button-div">
<button class="prev-button">⬅</button>
<button class="delete-button">âŒ</button>
<button class="next-button">âž¡</button>
</div>`;
newTaskDiv.setAttribute("task-value", param.value);
categorie.appendChild(newTaskDiv);
}
window.taskViewService = {
createTaskNode
};
})();
Related
I am storing the todo-items on an array of objects. I want to map the array to get the HTML elements. I am able to do this using innerHTML but I want to do it with createElement.
I am expecting <li><span>TEXT</span></li> output but I am getting only span. It seems append is not working.
HTML code :
<div class="todo">
<h1>Todo</h1>
<form name="todo--form">
<input type="text" name="todo--input" />
<button
type="submit"
name="todo--submit"
class="p-2 bg-slate-500 rounded"
>
Add
</button>
</form>
<ul class="todo--list"></ul>
</div>
JS Code :
const addTodoForm = document.querySelector(`[name="todo--form"]`);
const todoList = document.querySelector('.todo--list');
const todos = [];
// function getLabel() {}
function handleSubmit(e) {
e.preventDefault();
const text = this.querySelector(`[name="todo--input"]`).value;
const item = {
text: text,
finished: false,
};
if (text) {
todos.push(item);
addTodoForm.reset();
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.append(newSpan);
return newEl;
});
console.log(todoItems);
todoList.append(...todoItems);
}
return;
}
addTodoForm.addEventListener('submit', handleSubmit);
I am getting only <span><span> as output.
When the <form> is submitted there's only one item being appended to list at a time so there's no need to build a one item array. BTW event handlers (functions that are bound to an event) don't return values like a normal function can, but there are indirect ways to get values from them (like from your OP code, that array outside of the function pushing objects).
Details are commented in example
// Reference <form>
const form = document.forms.todo;
// Reference <ul>
const list = document.querySelector('.list');
// Define array for storage
let tasks = [];
// Bind "submit" event to <form>
form.onsubmit = handleSubmit;
// Event handlers passes Event Object by default
function handleSubmit(e) {
// Stop default behavior during a "submit"
e.preventDefault();
// Reference all form controls
const IO = this.elements;
/*
Define {item}
Add current timestamp to object {item}
Add <input> text to {item}
Add {item} to [tasks]
Clear <input>
*/
let item = {};
item.log = Date.now();
item.task = IO.data.value;
tasks.push(item);
IO.data.value = '';
/*
Create <time>
Assign timestamp <time>
Convert timestamp into a readable date and time
Add formatted datetime as text to <time>
*/
const log = document.createElement('time');
log.datetime = item.log;
let date = new Date(item.log);
log.textContent = date.toLocaleString();
/*
Create <output>
Add text of <input> to <output>
*/
const txt = document.createElement('output');
txt.value = item.task;
/*
Create <button>
Add type, class, and the text: "Delete" to <button>
*/
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'delete';
btn.textContent = 'Delete';
// Create <li>
const li = document.createElement('li');
// Append like crazy
li.append(log);
li.append(txt);
li.append(btn);
list.append(li);
console.log(tasks);
}
html {
font: 300 2ch/1.2 'Segoe UI';
}
input,
button {
font: inherit;
}
output {
display: inline-block;
margin: 0 8px 8px;
}
button {
cursor: pointer;
}
.as-console-row::after {
width: 0;
font-size: 0;
}
.as-console-row-code {
width: 100%;
word-break: break-word;
}
.as-console-wrapper {
max-height: 30% !important;
max-width: 100%;
}
<form id='todo'>
<fieldset>
<legend>ToDo List</legend>
<input id='data' type="text" required>
<button class="p-2 bg-slate-500 rounded">Add</button>
<ul class="list"></ul>
</fieldset>
</form>
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.append(newSpan);
return newLi;
});
In this block of code, I believe the newEl variable being created isn't correct. Try changing newLi.append(newSpan) to newLi.appendChild(newSpan)
This is also listed below
if (text) {
todos.push(item);
addTodoForm.reset();
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.appendChild(newSpan);
return newEl;
});
console.log(todoItems);
todoList.append(...todoItems);
}
return;
Thanks for all the answers.
Upon reading on the documentation of append and appendChild, it seems elements cannot be saved into a variable upon using these methods.
I just need to return the append li element.
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newLi.setAttribute('data-index', i);
const todoTextNode = document.createTextNode(todo.text);
newSpan.appendChild(todoTextNode);
newLi.appendChild(newSpan);
return newLi;
});
I was making a simple to-do list. You submit itens from an input and they go to the To-DO section. When you click over them they go to the 'Done' section. And when you click on them again, they vanish forever. It was all working fine.
But I realized the doneItens array kept growing in length, which I wanted to optimize. So I came up with this line of code
doneItens.splice(i, 1);
which goes inside an onclick event, which you can see in the code inside the deleteDone function.
That gives the error, though,
Error:{
"message": "Uncaught TypeError: doneItens.splice is not a function"
If I put it outside and below the onclick event it also doesn't work. How can I do it?
var input = document.getElementById('play');
var toDo = document.getElementsByTagName('ol')[0];
var done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
const newItem = document.createElement('li');
newItem.setAttribute('class', 'item');
newItem.append(input.value);
toDo.append(newItem);
input.value='';
deleteItem();
}
function deleteItem() {
const toBeDone = document.getElementsByClassName('item');
for(let i = 0; i < toBeDone.length; i++) {
toBeDone[i].onclick = () => {
appendItemDone(toBeDone[i]);
toBeDone[i].style.display = 'none';
deleteDone();
}
}
}
function appendItemDone(item) {
const newDone = document.createElement('li');
newDone.setAttribute('class', 'feito')
newDone.append(item.innerText);
done.append(newDone);
}
function deleteDone() {
const doneItens = document.getElementsByClassName('feito');
console.log('done length', doneItens.length)
for (let i = 0; i < doneItens.length; i++) {
doneItens[i].onclick = () => {
doneItens[i].style.display = 'none';
doneItens.splice(i, 1);
}
}
}
<div id='flex'>
<form class='form' onsubmit='handleSubmit(event)'>
<input placeholder='New item' type='text' id='play'>
<button>Send</button>
</form>
<div id='left'>
<h1 id='todo' >To-do:</h1>
<p class='instruction'><i>(Click over to mark as done)</i></p>
<ol id='here'></ol>
</div>
<div id='right'>
<h1>Done:</h1>
<p class='instruction'><i>(Click over to delete it)</i></p>
<p id='placeholder'></p>
<ol id='done'></ol>
</div>
</div>
With the use of JavaScript DOM API such as Node.removeChild(), Element.remove() and Node.parentNode, your task can be solved with this code:
const input = document.getElementById('play');
const todo = document.getElementById('todo');
const done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
// create new "todo" item
const newTodo = document.createElement('li');
newTodo.textContent = input.value;
todo.append(newTodo);
// clean the input field
input.value = '';
// listen to "click" event on the created item to move it to "done" section
newTodo.addEventListener('click', moveToDone);
}
function moveToDone(event) {
// remove "click"-listener to prevent event listener leaks
event.target.removeEventListener('click', moveToDone);
// move clicked todo-element to "done" section
const newDone = event.target.parentNode.removeChild(event.target);
done.append(newDone);
// listen to "click" event on the moved item to then completely delete it
newDone.addEventListener('click', removeFromDone);
debugElementsLeak();
}
function removeFromDone(event) {
// remove "click"-listener to prevent event listener leaks
event.target.removeEventListener('click', removeFromDone);
// complete remove clicked element from the DOM
event.target.remove();
debugElementsLeak();
}
function debugElementsLeak() {
const todoCount = todo.childElementCount;
const doneCount = done.childElementCount;
console.log({ todoCount, doneCount });
}
<div id="flex">
<form class="form" onsubmit="handleSubmit(event)">
<input placeholder="New item" type="text" id="play">
<button>Add item</button>
</form>
<div id="left">
<h1>To-do:</h1>
<p class="instruction"><em>(Click over to mark as done)</em></p>
<ol id="todo"></ol>
</div>
<div id="right">
<h1>Done:</h1>
<p class="instruction"><em>(Click over to delete it)</em></p>
<p id="placeholder"></p>
<ol id="done"></ol>
</div>
</div>
You'll want to use splice,
and then rather than use hidden, 'refresh' the done element by adding all elements in the spliced array.
I've commented my code where I've made changes and why
var input = document.getElementById('play');
var toDo = document.getElementsByTagName('ol')[0];
var done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
const newItem = document.createElement('li');
newItem.setAttribute('class', 'item');
newItem.append(input.value);
toDo.append(newItem);
input.value='';
deleteItem();
}
function deleteItem() {
const toBeDone = document.getElementsByClassName('item');
for(let i = 0; i < toBeDone.length; i++) {
toBeDone[i].onclick = () => {
appendItemDone(toBeDone[i].cloneNode(true));
toBeDone[i].style.display = 'none';
deleteDone();
}
}
}
function appendItemDone(item) {
const newDone = document.createElement('li');
newDone.setAttribute('class', 'feito')
newDone.append(item.innerText);
done.append(newDone);
}
function deleteDone() {
var doneItens = document.getElementsByClassName('feito');
for (let i = 0; i < doneItens.length; i++) {
doneItens[i].onclick = () => {
var splicedArray = spliceFromArray(doneItens,doneItens[i]);// NEW BIT -CALL NEW SPLICE FUNCTION
done.innerHTML=""; // NEW BIT - SET OVERALL DONE TO BLANK ON DELETE
for(var index in splicedArray){// NEW BIT - fOR EACH RETURNED ELEMENT IN THE SPLICE, ADD IT TO THE OVERALL DONE ELEMENT
done.appendChild(splicedArray[index]);
}
}
}
}
function spliceFromArray(arrayInput,element){// NEW BIT - SPLICE FUNCTION THAT RETURNS SPLICED ARRAY
var array = Array.from(arrayInput);
var index = array.indexOf(element);
if(index!=-1){
if(array.length==1 && index == 0){
array = [];
}
else{
array.splice(index,1);
}
}
return array;
}
<div id='flex'>
<form class='form' onsubmit='handleSubmit(event)'>
<input placeholder='New item' type='text' id='play'>
<button>Send</button>
</form>
<div id='left'>
<h1 id='todo' >To-do:</h1>
<p class='instruction'><i>(Click over to mark as done)</i></p>
<ol id='here'></ol>
</div>
<div id='right'>
<h1>Done:</h1>
<p class='instruction'><i>(Click over to delete it)</i></p>
<p id='placeholder'></p>
<ol id='done'></ol>
</div>
</div>
** I want when to click on the active button if the checkbox is checked to add filtered class in HTML element but it doesn't work and give me an undefined error in this line check.parentElement.classList.add("filtered"); **
<ul class="ul-list"></ul>
</section>
</main>
<footer class="footer">
<button class="all footer-btn">All</button>
<button class="active footer-btn">Active</button>
<button class="complete footer-btn">Complete</button>
</footer>
let check = document.querySelectorAll(".complete-txt");
let complete_btn = document.querySelector(".complete");
let active_btn = document.querySelector(".active");
let all_btn = document.querySelector(".all");
let edit_list = document.querySelector(".edit-list");
let main_text = document.querySelector(".main-text");
let list_item = document.querySelector(".list-item");
let footer = document.querySelector(".footer");
const generateTemplate = (todo) => {
const html = `
<li class="list-item">
<input type="checkbox" class="complete-txt" name="" id="check"><span class="main-text">${todo}</span><div class="edit-list"></div><div class="delete-list"></div>
</li>
`;
list.innerHTML += html;
};
// add todos event
addForm.addEventListener("submit", (e) => {
e.preventDefault();
const todo = addForm.add.value.trim();
if (todo.length) {
generateTemplate(todo);
addForm.reset();
}
});
active_btn.addEventListener("click", function () {
let check_id = document.querySelector(".complete-txt");
// check.forEach(function () {
debugger;
if (check.checked !== "true") {
check.parentElement.classList.add("filtered");
console.log("hi");
}
// });
// console.log("hi");
console.log("hi");
// console.log(check.checked.value);
});
if the larger document fixes all other inconcistencies you should be able to change the eventlistener to
active_btn.addEventListener("click", function () {
let check_id = document.querySelector(".complete-txt");
if (check_id.checked !== "true") {
check_id.parentElement.classList.add("filtered");
}
});
BUT!!! this will not "fix" all of your errors, like defining let check before the checkbox is created with generateTemplate
I need to write a code to add IDs to all element in one class. The IDs have to be based on innerText.
Elements look like that:
<lable class="sf-label-radio">Name1<span>Some Other Text that I do not need</span><label>
<lable class="sf-label-radio">Name2<span>Some Other Text that I do not need</span><label>
etc.
Here is my code:
<script>
addIDtoGI();
function addIDtoGI() {
let searchButtons = document.getElementsByClassName('sf-label-radio');
for(i = 0; i < searchButtons.length; i++) {
x = searchButtons[i].innerHTML;
x = x.substr(0, x.search("<")).replace(/\s+/g, '-').toLowerCase();
x = onlyEngLetters(x);
searchButtons[i].setAttribute('id',x);
}
}
function onlyEngLetters(text) {
text=text.replace("ę","e");
text=text.replace("ó","o");
text=text.replace("ą","a");
text=text.replace("ś","s");
text=text.replace("ł","l");
text=text.replace("ż","z");
text=text.replace("ź","z");
text=text.replace("ć","c");
text=text.replace("ń","n");
return text;
}
</script>
Thank You for help!
Iterate the childnodes until you get to the first textNode that isn't empty to get the text you want. Note also thaat replace() only works on first instance found and you probably want to convery to lower case to match your replacements
addIDtoGI()
function addIDtoGI(){
document.querySelectorAll('.sf-label-radio').forEach(el=>{
let txtNode = el.childNodes[0];
while(!txtNode.textContent.trim()){
txtNode = txt.nextSibling
}
el.id = onlyEngLetters(txtNode.textContent);
console.log(el.id)
});
}
function onlyEngLetters(text) {
return text.toLowerCase()
.replaceAll("ę","e")
.replaceAll("ó","o")
.replaceAll("ą","a")
.replaceAll("ś","s")
.replaceAll("ł","l")
.replaceAll("ż","z")
.replaceAll("ź","z")
.replaceAll("ć","c")
.replaceAll("ń","n")
}
<label class="sf-label-radio">Name1<span>Some Other Text that I do not need</span><label>
<label class="sf-label-radio">Name2<span>Some Other Text that I do not need</span><label>
First, you define your function but you never call it.
In your script, add the "()" to "addIDToGI;": addIGToGI();
There's also a typo on searchButtons.lenght, it should be length.
It should resolve your errors.
EDIT: Also, as someone mentionned in the comments, <lable> should be <label>.
First you must call function with () and when using for loop must use variables=>
for(let i = 0; i < searchButtons.length; i++) . length is true not lenght. and of course
let x = searchButtons[i].innerHTML;
and ...
const addIdToClassByInnerHTML = cls => {
const elements = document.querySelectorAll('.' + cls);
elements.forEach(el=>{
el.setAttribute('id', el.innerHTML);
});
}
note that do not use this function if the element contains child.
if your element contains one or more child(ren), use this:
const addIdToClassByInnerHTML = cls => {
const elements = document.querySelectorAll('.' + cls);
elements.forEach(el=>{
let html = el.innerHTML;
el.querySelectorAll('*').forEach(sub=>{
html = html.replace(sub.outerHTML, '');
});
el.setAttribute('id', html);
});
}
codepen demo
snippets
const addIdToClassByInnerHTML = cls => {
const elements = document.querySelectorAll('.' + cls);
elements.forEach(el=>{
let html = el.innerHTML;
el.querySelectorAll('*').forEach(sub=>{
html = html.replace(sub.outerHTML, '');
});
el.setAttribute('id', html);
});
}
addIdToClassByInnerHTML('sf-label-radio');
console.log(document.querySelectorAll('.sf-label-radio')[0]);
label{
display: block;
}
<label class="sf-label-radio">Name1<span>Some Other Text that I do not need</span><label>
<label class="sf-label-radio">Name2<span>Some Other Text that I do not need</span><label>
I am creating a simple note editor that has two divs a heading and a body. I'm trying to add a new note by creating the two divs with a button. Such that when you click the button the new divs will be created with texts appended to it through localStorage. When I click the button none of the divs is added. here is the html
<div id ="heading" contenteditable="true">
</div>
<div id="content" contenteditable="true">
</div>
<button type="button" onclick="addNote()" name="button">Add new Note</button>
here is the js
document.getElementById("heading").innerHTML = localStorage['title'] || 'Heading goes here';
document.getElementById('content').innerHTML = localStorage['text'] || 'Body of text editor';
setInterval(function () {
localStorage['title'] = document.getElementById('heading').innerHTML;
localStorage['text'] = document.getElementById('content').innerHTML;
}, 1000);
function addNote() {
const heading = document.createElement('div');
const content = document.createElement('div');
heading.id = "heading";
content.id = "content";
localStorage['title'] = document.getElementById('heading').innerHTML;
localStorage['text'] = document.getElementById('content').innerHTML;
}
your are missing to append the created elements to the dom (https://www.w3schools.com/jsref/met_node_appendchild.asp) :
function addNote() {
var heading = document.createElement('div');
var content = document.createElement('div');
heading.id = "heading";
content.id = "content";
document.getElementById("myDivs").appendChild(heading);
document.getElementById("myDivs").appendChild(content);
}
and you will need to have an div with id myDivs
<div id="myDivs"></div>
<button type="button" onclick="addNote()" name="button">Add new Note</button>
greetings
There are many problems in your code.
Proper way to set localStorage is via localStorage.setItem(key, val)
You need to append your DIVs somewhere on your HTML page.
Also you need to handle your setTimeout properly.
Please find solution below
HTML
<button type="button" onclick="addNote()" name="button">Add new Note</button>
JavaScript
var intervalObj = null;
function addNote() {
if(intervalObj) {
clearInterval(intervalObj)
}
const heading = document.createElement('div');
heading.setAttribute('contenteditable', true)
heading.id = "heading";
const content = document.createElement('div');
content.setAttribute('contenteditable', true)
content.id = "content";
heading.innerHTML = window.localStorage.getItem('title') || 'Heading goes here';
content.innerHTML = window.localStorage.getItem('text') || 'Body of text editor';
document.getElementsByTagName("body")[0].append(heading);
document.getElementsByTagName("body")[0].append(content);
intervalObj = setInterval(function () {
localStorage.setItem('title', document.getElementById('heading').innerHTML);
localStorage.setItem('text', document.getElementById('content').innerHTML);
}, 1000);
}
Instead of using the setInterval function you can load the data using an onload function and update to local storage whenever the AddNote button is clicked.
Modify the html as follows:
<body onload="load()">
<div id ="heading" contenteditable="true">
</div>
<div id="content" contenteditable="true">
</div>
<button type="button" onclick="addNote()" name="button">Add new Note</button>
</body>
And also you can modify your JS file as follows
function load() {
var headingText = localStorage['title'] || 'Heading goes here';
var bodyText = localStorage['text'] || 'Body of text editor';
document.getElementById("heading").innerHTML = headingText;
document.getElementById('content').innerHTML = bodyText;
}
function addNote() {
localStorage['title'] = document.getElementById('heading').innerHTML;
localStorage['text'] = document.getElementById('content').innerHTML;
}