Creating multiple elements using javascript with some element in created element - javascript

I'm trying to use javascript to create a set of elements over and over again once the user enters a text where it would display the text with a button with an image in it to the side of it but I could not find a way to do it efficiently.
The current method I was going to create would require each element to have a id tag to it so that I could call appendChild to join the elements together.
I also need to have a create element be appended into another create element which adds to the issues
This is what I'm trying to achieve in the html code (the div would not be needed to be created as it is in the html code already)
function addToList(input) {
console.log(x);
let task = document.createElement('p');
task.id = x;
task.appendChild(document.createTextNode(input));
document.getElementById('listOfTasks').appendChild(task);
addCheckBox(x);
x++;
}
function addCheckBox(id) {
let checkBox = document.createElement('a');
checkBox.className = 'button is-rounded is-small';
checkBox.id = 'checkBox';
document.getElementById(id).appendChild(checkBox);
let a = document.createElement('span');
a.className = 'icon is-small';
a.id = 'apple';
document.getElementById(id).appendChild(a);
let b = document.createElement('i');
b.className = 'fas fa-check';
document.getElementById(id).appendChild(b);
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css"/>
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<div class="container">
<div id="listOfTasks"></div>
</div>
<section class="section">
<div class="container">
<div class="field box form-popup" id="addTask">
<div class="control">
<div class="field is-grouped">
<label class="label"><b>Task to add</b></label>
</div>
<input
type="text"
class="input"
placeholder="Enter Task"
id="task"
required
/>
</div>
<button
type="button submit"
class="button is-success"
id="submit"
onclick="closeForm()"
>
Add
</button>
</div>
</div>
</section>
The current output is shown as
Would be grateful if anyone knows a better method to do this

Make a function that reduces boilerplate code when creating element
function create(name, props, children) {
let elem = document.createElement(name); // create it
const parent = props.parent // we use parent prop elsewhere
let keys = Object.keys(props) // collect keys
keys = keys.filter(function(key) { // remove parent prop from keys
return key !== 'parent'
})
keys.forEach(function(key) { // assign props to element
elem.setAttribute(key, props[key])
})
if (children && children.length) { // add children to element
children.forEach(function(child) {
elem.appendChild(child)
})
}
if (parent) { // attach to parent
document.getElementById(id).appendChild(elem);
}
return elem // return it, to customize further
}
And then
function addCheckBox(id) {
create('a', {
id: 'checkBox', // simple prop
parent: id, // parent prop
class: 'button is-rounded is-small' // simple prop
})
var span = create('span', {
parent: id,
id: 'apple',
class: 'icon is-small'
}, [create('i', { // demo of children
class: 'fa fa-check'
}])
span.setAttribute('data-something', 1) // demo of customizing
}

Related

How to avoid values from an object after inserting from an input form to be inserted repeatedly?

I am inserting the values of an input into an array of objects. Then, I want to get those values e show inside the HTML. Inserting each value inside the object is not the problem, every time I click the button, each value is successfully added. When I console.log() the array, it shows only one of each value added. The problem is when I try to show the content of the object inside the HTML element, it inserts all the data from the object over and over again, but I just want to add the last value added and keep what was previously inserted, not to add everything again.
What am I doing wrong?
This is my HTML
<main>
<div class="add-recipes">
<form id="form">
<h2>Add Recipe</h2>
<div class="input-wrapper">
<div class="text-input-wrapper">
<label for="title"
>Title
<input type="text" name="title" id="recipe-title" />
</label>
</div>
</div>
<button id="send-recipe-btn" type="submit">Send Recipe</button>
</form>
</div>
<div class="recipes-container"></div>
</main>
This is my JS File
let recipes = [];
const addRecipe = e => {
e.preventDefault();
let recipe = {
title: document.getElementById('recipe-title').value
};
recipes.push(recipe);
document.querySelector('form').reset();
recipes.forEach(e => {
const recipeContainer = document.querySelector('.recipes-container');
const recipeTitle = document.createElement('div');
recipeTitle.classList.add('recipe-title');
recipeContainer.append(recipeTitle);
recipeTitle.textContent = e.title;
});
console.log(recipes);
};
document.getElementById('send-recipe-btn').addEventListener('click', addRecipe);
Thanks for any tip or help to solve this.
Have the forEach()loop to start before recipeTitle.textContent = e.title;
let recipes = [];
const addRecipe = e => {
e.preventDefault();
let recipe = {
title: document.getElementById('recipe-title').value
};
recipes.push(recipe);
document.querySelector('form').reset();
const recipeContainer = document.querySelector('.recipes-container');
const recipeTitle = document.createElement('div');
recipeTitle.classList.add('recipe-title');
recipeContainer.append(recipeTitle);
recipes.forEach(e => {
recipeTitle.textContent = e.title;
});
console.log(recipes);
};
document.getElementById('send-recipe-btn').addEventListener('click', addRecipe);
In your event handler, you are looping over the recipes array and creating a new element every single time the button is pressed.
Just remove the loop and it will work properly

How could I target these elements in javascript? (more below)

I have some To Do-s that are dinamically created by the user:
<div>
<h3 class = 'taskTitle'>do homework </h3>
<p class = 'taskDate'>Expires: 2021.12.31</p>
<input type = button class = 'delBtn' value = 'x'></input>
<input type = button class = 'expandBtn' value = '...'></input>
</div>
<div>
<h3 class = 'taskTitle'>workout </h3>
<p class = 'taskDate'>Expires: 2021.10.11</p>
<input type = button class = 'delBtn' value = 'x'></input>
<input type = button class = 'expandBtn' value = '...'></input>
</div>
**etc.**
On click of the expandBtn a pop up window appears that would contain the title (h3) and the date (p) of the specific To Do.
script:
function showDescription(){
const expandBtns= document.querySelectorAll('.expandBtn')
expandBtns.forEach(btn => {
btn.addEventListener('click', function(event){
let popUp = document.createElement('div')
popUp.classList.add('descriptionBox')
let title = event.target.parentNode.firstChild.textContent **<--says its undefined**
let date = event.target.parentNode.firstChild.textContent **<--says its undefined**
popUp.innerHTML = `
<h3>${title}</h3>
<p class = 'description'> lorem ipsum </p>
<p class = 'dateDescription'>${date}</p>
<input class = 'delDescription' type = button value = 'x'></input>`
const todos = document.querySelector('#todos')
todos.appendChild(popUp)
//close button for popUp
const delDescription = document.querySelectorAll('.delDescription')
delDescription.forEach(btn => {
btn.addEventListener('click', function (event){
event.target.parentNode.remove()
})
})
// alert(document.querySelector('.activeProject').textContent)
})
})
}
So how could I target them? querySelector isn't good either, as I have more than 1 To Do-s. Any help appreciated!
You could add a unique id or class to all the div elements. For example:
<div id="to-do-1"><p>Test</p></div>
<button onclick="myFunction()">Click Me!</button>
<script>
function myFunction() {
document.getElementById("to-do-1").style.display = "inline";
}
</script>
<style>
#to-do-1 {
display: none;
}
</style>
Select all expandBtn
var btn = document.getElementsByClassName("expandBtn")
Create a loop and addEventListener for all of them
for(let i =0; i < btn.length; i++) {
btn[i].addEventListener("click", function () {
let h3 = this.parentNode.getElementsByTagName("h3")[0];
let p = this.parentNode.getElementsByTagName("p")[0];
alert("h3 = "+ h3.innerText + " & p = " + p.innerText);
}
}
now when the user clicking on anyone of them it's will search for h3, p who is belong to the parent of this button
Using .children will get you what you want:
document.addEventListener("click",ev=>{
if (ev.target.className!=="expandBtn") return;
const [name,date]=[...ev.target.parentNode.children].slice(0,2).map(e=>e.textContent)
console.log(name,date);
// further code for popup ...
})
<div>
<h3 class = 'taskTitle'>do homework </h3>
<p class = 'taskDate'>Expires: 2021.12.31</p>
<input type = button class = 'delBtn' value = 'x'></input>
<input type = button class = 'expandBtn' value = '...'></input>
</div>
<div>
<h3 class = 'taskTitle'>workout </h3>
<p class = 'taskDate'>Expires: 2021.10.11</p>
<input type = button class = 'delBtn' value = 'x'></input>
<input type = button class = 'expandBtn' value = '...'></input>
</div>
.firstChild will return the first childNode, which in your case would have been a blank and a line-break and not the <h3> element you expected.
.children on the other hand will return a collection of all child elements (like: <div>, <p>, <span>, ... etc.), the chained .slice(0,2) will slice off the first two elements only.

Each tasks added would have his own property

I have created a todo-apps with js but I have a problem : when I am clicking on the check button to do someting or on the edit button or the time button all tasks are changed : for example when I click on the check button on « learn js » I want that just this task changed ( underline) but when I do that all my tasks become underline. I know this is a beginner question sorry. This is my HTML code :
<h1>To Do List</h1>
<input type="text" placeholder="Name..." id="Name">
<input type="button" id="addItem" value="Add Item" />
<div class="choices">
<p id="p"></p>
</div>
<button id="btn" type="submit"> Clear Items</button>
This is my JS code :
let clear = document.getElementById("btn");
let add = document.getElementById("addItem");
let choices = [];
let vide = document.getElementById('p');
var choice = document.getElementById("Name").value;
let invalid = document.getElementById("invalid");
function main() {
add.addEventListener('click', function() {
addItems();
})
}
function addItems() {
choice = document.getElementById("Name").value;
vide.innerHTML += choice;
choices.push(choice);
document.getElementById('p').insertAdjacentHTML('beforeend', `<i id="check" class="far fa-check-circle"></i>`);
document.getElementById('p').insertAdjacentHTML( 'beforeend', `<i id="null" class="far fa-times-circle"></i>`);
document.getElementById('p').insertAdjacentHTML( 'beforeend', `<i. id="edit" class="far fa-edit"></i>`);
vide.insertAdjacentHTML('beforeend', `<br/><br/>`);
document.getElementById('p').classList.add('listClass');
document.getElementById('check').onclick = function() {
document.getElementById('p').classList.toggle("done");
document.getElementById('check').classList.toggle('opacity');
};
document.querySelector('#null').onclick = function() {
vide.innerHTML ='';
};
document.getElementById('edit').onclick = function() {
// I have not finished this part
}
}
}
main();
This is a picture of the result :
Depsite the fact that you have many mistakes(especially code redundancy) in your code, the main issue is that your IDs are not unique in the page.
As I said, the code is not that flexible to allow us building on it, so I took sometime to provide you a more modern, readable, performant solution (regarding yours of course !).
The code is commented to allow you understand easily what I'm doing.
// selecting the elements from the DOM
const todoList = document.getElementById("todo-list"),
newTodoForm = document.getElementById("new-todo-form"),
todoInp = document.getElementById("todo"),
clearBtn = document.getElementById("clear"),
choices = [],
/**
* createElement: a function that creates an HTML element with the specified attributes and events
* #param nodeName (string) the HTML element tag name
* #param opts (object) the attributes object which can contain :
* content: (object) an object to specify the element's content and it has two fields :
* html: (boolean) is the content should be inserted as HTML or a simple text. Defaults to false, pass true to treat the content as HTML
* value: (string) the actual content
* classList: (string) specifies a space-separated classes to be assigned to the element
* id: (string) the elemnt's ID
* data: (object) an object for the "data-TOKEN=VAL" attributes where each key (in camelCase) is the "TOKEN" and its value is the "VAL", example: {someDataOne: 'some value one', someDataTwo: 'some value two'} will be transformed into "data-some-data-one="some value one" data-some-data-two="some value two""
* events: (object) the keys are event names and the values are the events callbacks. Example {click: () => alert('clicked !')}
**/
createElement = (nodeName, opts) => {
// create the requested element
const el = document.createElement(nodeName);
// merge the options with the default ones
opts = Object.assign({
content: {
value: "",
html: !1
},
classList: "",
id: "",
data: {},
events: {}
},
opts
);
// apply the classes if the "opts.classList" is not empty
!!opts.classList && (el.classList = opts.classList);
// apply the ID if the "opts.id" is not empty
!!opts.id && (el.id = opts.id);
// apply the content if the "opts.content.value" is not empty and check if we want the content to be treated as HTML
!!opts.content.value &&
(el[opts.content.html === !0 ? "innerHTML" : "textContent"] =
opts.content.value);
// apply the data-* if the "opts.data" is not empty
if (Object.getOwnPropertyNames(opts.data).length) {
for (let p in opts.data)
if (opts.data.hasOwnProperty(p)) el.dataset[p] = opts.data[p];
}
// assign the events if the "opts.events" is not empty
if (Object.getOwnPropertyNames(opts.events).length) {
for (let p in opts.events)
if (opts.events.hasOwnProperty(p))
el.addEventListener(p, opts.events[p]);
}
// return the created element after applying the requested attributes and events
return el;
},
/*
* a function that generates a list-item template (HTML code that contains the todo text, buttons for edit, delete and so on...)
* #param txt (string) the todo text
*/
todoItemTpl = txt => {
// "item" is the list-item wrapper that contains all the buttons and todo text for only one todo item
// as you can see, we're using the "createElement" function so the code doesn't get redundant and also it become more readable and flexible
const item = createElement("div", {
classList: "todo-item row col-12 py-2 px-0 m-0 my-1 align-items-center"
}),
// the "p" element that shows the todo text
txtWrapper = createElement("p", {
content: {
value: txt
},
classList: "todo-text col-auto m-0"
}),
// a wrapper for the options (delete, edit and check) buttons of a todo item
btnsWrapper = createElement("div", {
classList: "todo-btns col-auto"
}),
// an array that holds the 3 option buttons so later we can loop through it and append each button to "btnsWrapper"
// every button here has its click event so the browser won't get confused which todo item should it alter
// !!: the edit functionality is not implemented it simply prints some text to the console when the edit button is clicked
optionBtns = [
createElement("button", {
content: {
value: '<i class="far fa-check-circle"></i>',
html: !0
},
classList: "option-btn check-btn ml-1",
events: {
click: function() {
this.closest('div.todo-item').classList.add('finished');
this.disabled = !0;
}
}
}),
createElement("button", {
content: {
value: '<i class="far fa-edit"></i>',
html: !0
},
classList: "option-btn edit-btn ml-1",
events: {
click: function() {
console.log('Edit functionnality not yet implemented !');
}
}
}),
createElement("button", {
content: {
value: '<i class="fa fa-times"></i>',
html: !0
},
classList: "option-btn del-btn ml-1",
events: {
click: function() {
const todoItem = this.closest('div.todo-item'),
txt = todoItem.querySelector('.todo-text').textContent;
todoItem.remove();
choices.splice(choices.indexOf(txt), 1);
}
}
})
];
// append the option buttons to the buttons wrapper
optionBtns.forEach((b) => btnsWrapper.appendChild(b));
// append the todo text to the todo-item
item.appendChild(txtWrapper);
// append the option buttons wrapper text to the todo-item
item.appendChild(btnsWrapper);
// return the newly created todo-item
return item;
};
// listen for the form submission
newTodoForm.addEventListener("submit", e => {
// store the trimmed input (the todo name) value
const inpVal = todoInp.value.trim();
// prevent form submission to disable page reload
e.preventDefault();
// stop execution if the todo item name is empty
if (!inpVal) return;
// if the todo text is not empty then :
// append the entered value to the "choices" array
choices.push(inpVal);
// append the todo-item to the todo list (that is initially empty) using "todoItemTpl" function and by passing the entered value for the todo name
todoList.appendChild(todoItemTpl(inpVal));
// finnaly, empty up the todo name input field
todoInp.value = "";
});
// listen for the click event of the clear button
// when clicked, remove all the todo-items and empty up the "choices" array
clearBtn.addEventListener('click', () => (todoList.innerHTML = '', choices.length = 0));
// styling for the demo, nothing fancy !
.todo-list * {
transition: all .4s 0s ease;
}
.todo-list {
background-color: #f5f5f5;
}
.todo-list .todo-item {
border-bottom: 1px solid #c5c5c5;
}
.todo-list .todo-item.finished {
background-color: #65d4a8;
}
.todo-list .todo-item.finished .todo-text {
text-decoration: line-through;
}
<!-- importing Bootsrap and Font-Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" />
<!-- some transformations to use Bootsrap's classes -->
<div class="todo-form-wrapper">
<form action="#" id="new-todo-form">
<h1>To Do List</h1>
<input type="text" placeholder="Name..." id="todo">
<button type="submit" value="Add Item">Add</button>
</form>
</div>
<!-- the todo list wrapper is initially empty -->
<div id="todo-list" class="todo-list row m-0"></div>
<button id="clear" type="button">Clear Items</button>
Eventhough the code looks good, I don't recommand using it in production as it has some issues, it doesn't support old browsers like IE for example.
I guess you are adding all the contents inside one single - p - tag and when you change the class- i.e: toggling the class of that - p - tag to "done", it is getting applied to all inner texts.
You need separate wrapper for each task assertion to handle this problem.

How to add task to current method?

I am trying to do a web app similar to google calendar. I have done the object and methods within it but now it's time to be able to add what I want as a task. My idea is for the user to add something to the input and that input being console.logged for now.
Any idea?
HTML
<div class="new-task" id="task-input">
<div id="add-new-task">Task: <input type="text"></div>
<div id="add-time">Time: <input type="text"></div>
<button class ="save-task" onclick="">Save task</button>
</div>
Javascript
var idCounter = 0
var tasksManager = {
array: [],
add: function(task){
taskObject = {
title: task,
idVerification: idCounter ++
}
tasksManager.array.push(taskObject)
},
show:function(id){
var i;
for (i = 0; i < tasksManager.array.length; i++) {
if(id === tasksManager.array[i].idVerification){
return tasksManager.array[i]
}
}
},
delete:function(task){
if(this.show){
tasksManager.array.splice(task)
}
}
}
var newTask = document.getElementById("add-new-task")
newTask.addEventListener('click',tasksManager.add())
console.log(tasksManager.array)
As you can see with console.log above the array index [0] is logged as undefined but I wanted the user to write in the input " Go to the gym" and this to be logged within the array.
Thanks
Some issues:
You are not assigning the click handler. Instead you execute it immediately (not on click).
When you call .add() you don't provide an argument: the name of the task
The click handler should be on the button element, not on the div that has the input element. And so it will be useful to give that button an id attribute.
You should retrieve the value from the input element, and so it would be more appropriate to give that element an id and not so much the div that wraps it.
The console.log at the end of your script is executed immediately. It should be done only when the user has clicked the button.
Snippet with some corrections (also in the HTML!):
var idCounter = 0
var tasksManager = {
array: [],
add: function(task){
let taskObject = {
title: task,
idVerification: idCounter ++
}
tasksManager.array.push(taskObject)
},
show:function(id){
var i;
for (i = 0; i < tasksManager.array.length; i++) {
if(id === tasksManager.array[i].idVerification){
return tasksManager.array[i]
}
}
},
delete:function(task){
if(this.show){
tasksManager.array.splice(task)
}
}
}
var button = document.getElementById("save-task"); // <-- the button
var input = document.getElementById("add-new-task"); // <-- the input (move the ID attribute to the input!)
button.addEventListener('click', () => {
tasksManager.add(input.value);
console.log(tasksManager.array)
})
<div class="new-task" id="task-input">
<div >Task: <input id="add-new-task" type="text"></div>
<div id="add-time">Time: <input type="text"></div>
<button class ="save-task" id ="save-task" onclick="">Save task</button>
</div>

How to change inner div's when cloning a template?

I have a template that I'm cloning to make Single Page Application. Inside this template are some div's that should have a unique id's so that it should be working individually when I open multiple apps(clone multiple divs)
<template id="templ">
<div id="name"></div>
<div id="btn">
<fieldset id="fld">
<input type="text" id="userMessage"placeholder="Type your message…" autofocus>
<input type="hidden">
<button id="send" >Save</button>
</fieldset>
</div>
</template>
and I'm cloning it like this
var i =0
let template = document.querySelector('#templ')
let clone = document.importNode(template.content, true)
clone.id = 'name' + ++i // I can't change the Id o this name div
document.querySelector('body').appendChild(clone)
Thanks
clone.id is undefined since clone is a #document-fragment with two children.
You need to query the 'name' child and change its id, for example like this:
const template = document.querySelector('#templ')
const body = document.querySelector('body')
function appendClone(index){
let clone = document.importNode(template.content, true)
clone.getElementById('name').id = 'name' + index
// all other elements with IDs
body.appendChild(clone)
}
Then you can iterate over the amount of clones and simply call the function with the loop index:
let clones = 5
for (let i = 0; i < clones; i++){
appendClone(i)
}
store the dynamic HTML data in script element and add when ever required by replaciing with dynamic data.
HTML Data:
<script id="hidden-template" type="text/x-custom-template">
<div id='${dynamicid}'>
<p>${dynamic_data}</p>
</div>
</script>
Script to replace and append.
var template_add = $('#hidden-template').text();
var items = [{
dynamicid: '1',
dynamic_data: '0'
}];
function replaceDynamicData(props) {
return function(tok, i) {
return (i % 2) ? props[tok] : tok;
};
}
var dynamic_HTML = template_add.split(/\$\{(.+?)\}/g);
$('tbody').append(items.map(function(item) {
return dynamic_HTML.map(replaceDynamicData(item)).join('');
}));

Categories

Resources