How to remove elements from an array of objects - javascript

In a todo app I'm making, I want to remove the completed tasks by clicking on a button.
This is what I have in my body:
<div id="div-todo"></div>
<button id="hide-todo">Hide completed</button>
<script src="script.js"></script>
</body>
And this is my js code:
const todos = [
{
text: 'Learn HTML',
completed: true
},{
text: 'Learn CSS',
completed: true
},{
text: 'Learn Javascript',
completed: false
},{
text: 'Learn Nodejs',
completed: false
},{
text: 'Learn SQL',
completed: false
}
]
// renderring and printing tasks
const renderTodo = function (todos) {
const incompletedTodo = todos.filter(function (todo) {
return !todo.completed
})
//printing the h2 tag that containe the number of incompleted task
const summery = document.createElement('h2')
summery.textContent = `You have ${incompletedTodo.length} todos left. `
document.querySelector('#div-todo').appendChild(summery)
//printting all of tasks
todos.forEach(function (todo) {
const p = document.createElement('p')
p.textContent = todo.text
document.querySelector('#div-todo').appendChild(p)
})
// button setting
document.querySelector('#hide-todo').addEventListener('click',function (e) {
const completedTodo = todos.filter(function (todo) {
return todo.completed
})
e.target.textContent = 'Completed task is hide'
console.log(completedTodo)
})
}
renderTodo(todos)
I want to hide uncompleted task with a button, I even log the completedTodo variable, but I don't know how to remove them (I am new to JS).

The problem is that there is no 'real' connection between the objects in your todos array and the actual html elements. So though a task might be flagged as completed you don't know what paragraph element is displaying the task.
To make a connection, get all child elements of the parent element holding all your paragraphs (div-todo), check if it's text matches the text in your todos array and ultimately remove the whole paragraph element if the task is flagged as completed.
document.querySelector('#hide-todo').addEventListener('click', function(e) {
const completedTodo = todos.filter(function(todo) {
return todo.completed
})
e.target.textContent = 'Completed task is hide'
var myChild = document.getElementById("div-todo").childNodes;
for (var a = 0; a < myChild.length; a++) {
for (var b = 0; b < completedTodo.length; b++) {
if (myChild[a].textContent == completedTodo[b].text) {
myChild[a].parentNode.removeChild(myChild[a]);
}
}
}
})

Just assing incompleted task in to do.
const todos = [{
text: 'Learn HTML',
completed: true
},{
text: 'Learn CSS',
completed: true
},{
text: 'Learn Javascript',
completed: false
},{
text: 'Learn Nodejs',
completed: false
},{
text: 'Learn SQL',
completed: false
}]
// renderring and printing tasks
const renderTodo = function(todos){
const incompletedTodo = todos.filter(function (todo) {
return !todo.completed
})
//printing the h2 tag that containe the number of incompleted task
const summery = document.createElement('h2')
summery.textContent = `You have ${incompletedTodo.length} todos left. `
document.querySelector('#div-todo').appendChild(summery)
//printting all of tasks
todos.forEach(function (todo) {
const p = document.createElement('p')
p.textContent = todo.text
document.querySelector('#div-todo').appendChild(p)
})
// button setting
document.querySelector('#hide-todo').addEventListener('click',function (e){
const completedTodo = todos.filter(function (todo) {
return todo.completed
})
e.target.textContent = 'Completed task is hide'
$('#div-todo').html('');
incompletedTodo.forEach(function (todo) {
const p = document.createElement('p')
p.textContent = todo.text
document.querySelector('#div-todo').appendChild(p)
})
console.log(completedTodo)
})
}
renderTodo(todos)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
<div id="div-todo"></div>
<button id="hide-todo">Hide completed</button>
<script src="script.js"></script>
</body>

Related

Manipulate DOM with Javascript, do the same for multiple elements without copy paste

I have code which I don't want to nearly copy paste, instead I want an short alternative.
I have this 4 elements, which all should be manipulated, I want to place an Input inside them and edit some arguments.
Now I want to run the same code for each of these 4.
const tableTaskName = document.getElementById('taskName_' + taskID);
const tableStartTime = document.getElementById('startDate_' + taskID);
const tableEndTime = document.getElementById('endDate_' + taskID);
const tableStatus = document.getElementById('status_' + taskID);
Below are examples where I used tableTaskName.
The task is the parameter my function takes, and is one task of the tasks Array on the bottom.
The type should be text for tableTaskName and tableStatus, for tableStartTime and tableEndTime it should be time.
tableTaskName.innerHTML = ''
tableTaskName.textContent = task.taskName;
const inputTaskName = document.createElement('input');
inputTaskName.type = 'text';
var tasks = [{ 'id': 1, 'taskName': 'INIT', 'startDate': new Date('1970-01-01T00:00:00Z'), 'endDate': new Date('1970-01-01T00:00:10Z'), 'duration': new Date('1970-01-01T00:00:10Z'), 'status': 'RUNNING'},
{ 'id': 2, 'taskName': 'INIT', 'startDate': new Date('1970-01-01T00:00:00Z'), 'endDate': new Date('1970-01-01T00:00:10Z'), 'duration': new Date('1970-01-01T00:00:10Z'), 'status': 'RUNNING'} ]
Is the only possible method to take copy paste the code? I'd like to do it without that, if possible.
In JavaScript you can grab an array of elements by using document.getElementsByClassName().
So if you have multiple elements with the same class name it will fill the array.
Then just loop over the array and it will apply the code to all elements.
Like this:
<div class="divElement"></div>
<div class="divElement"></div>
<div class="divElement"></div>
<script>
var elements = document.getElementsByClassName("divElement");
for (var i = 0; i < elements.length; i++){
// code to edit elements
}
</script>
You can make an array of property names and then make two for loops to go through properties and task:
const props = ['taskName', 'startDate', 'endDate', 'status']
...
for (let task of tasks) {
for (let prop of props) {
const tableProp = document.getElementById(prop + '_' + task.id)
tableProp.innerHTML = ''
tableProp.textContent = task[prop]
const inputTaskName = document.createElement('input')
if (prop == 'startDate' || prop == 'endDate') {
inputTaskName.type = 'time'
} else {
inputTaskName.type = 'text'
}
tableProp.appendChild(inputTaskName)
}
}
Here is a working example. I just assumed the html layout:
const props = ['taskName', 'startDate', 'endDate', 'status']
const tasks = [{
id: 1,
taskName: 'INIT',
startDate: new Date('1970-01-01T00:00:00Z'),
endDate: new Date('1970-01-01T00:00:10Z'),
duration: new Date('1970-01-01T00:00:10Z'),
status: 'RUNNING',
},
{
id: 2,
taskName: 'Another task name',
startDate: new Date('1970-01-01T00:00:00Z'),
endDate: new Date('1970-01-01T00:00:10Z'),
duration: new Date('1970-01-01T00:00:10Z'),
status: 'stopped',
},
]
for (let task of tasks) {
for (let prop of props) {
const tableProp = document.getElementById(prop + '_' + task.id)
tableProp.innerHTML = ''
tableProp.textContent = task[prop]
const inputTaskName = document.createElement('input')
if (prop == 'startDate' || prop == 'endDate') {
inputTaskName.type = 'time'
} else {
inputTaskName.type = 'text'
}
tableProp.appendChild(inputTaskName)
}
}
/* Just for demo */
.task-block {
background-color: lightgrey;
padding: 4px;
}
.task-block p {
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 4px;
}
<div class="task-block">
<p id="taskName_1"></p>
<p id="startDate_1"></p>
<p id="endDate_1"></p>
<p id="status_1"></p>
</div>
<br />
<div class="task-block">
<p id="taskName_2"></p>
<p id="startDate_2"></p>
<p id="endDate_2"></p>
<p id="status_2"></p>
</div>
U just take the elements and pass them as an argument to the render function. U can manipulate each element as u wish by adding this or that data. As an output u`ll get the array of those items.
let x = document.querySelector('#taskName');
let y = document.querySelector('#taskName2');
renderElement = (...elements) => {
elements.forEach(element => {
const inputTaskName = document.createElement('input');
inputTaskName.type = 'text';
element.appendChild(inputTaskName)
})
return [elements];
}
console.log(renderElement(x, y))

How to open url in new tab from JavaScript script

I am developing a chatbot using peekabot (link provided below for documentation and official example), and want to be able to open a URL in a new tab (as you would have the option to do using plain html) when a user clicks on an option. Linked with this is the fact that the URL button that is generated is styled wrong (shows up as blank without the label text on mouse-hover-over).
I have included the whole script below for context, but the bit I am referring to is:
6: {
text: 'You should check it out on test preview',
options: [{
text: "PREVIEW THIS LINK",
url: "www.google.com"
}]
},
The above is option 6 and on clicking it the user should go to the URL (e.g. google.com) but open in a new tab and also not be blank (the button link is blank on hover over for some reason)
I have tried: url: "www.google.com" target="_blank (see below) but this breaks the whole javascript code.
6: {
text: 'You should check it out on test preview',
options: [{
text: "PREVIEW THIS LINK",
url: "www.google.com" target="_blank
}]
},
I have also tried:
url: "www.google.com target="_blank"
and
url: "www.google.com target=_blank"
neither works.
For an answer:
Solution for opening the URL in a new tab
Correction so that the button link (for the URL) is not BLANK on hover-over. (when you move the mouse away from the button link, the text appears)
This is the official site - https://peekobot.github.io/peekobot/ but irritatingly even in their example - the url to GitHub is opened in the same tab.
The whole script below for context:
</script>
<script type="text/javascript" src="<?php echo base_url(''); ?>js/bot.js>v=1"></script>
<script>
const chat = {
1: {
text: 'Some Text here',
options: [{
text: 'More Text here',
next: 101
},
{
text: '??',
next: 201
}
,
{
text: '?????',
next: 301
}
]
},
101: {
text: 'Some information here,
options: [{
text: 'That is great, thanks!',
next: 102
},
{
text: 'I would like some more info please.',
next: 103
}]
},
102: {
text: 'Thanks and Goodbye',
url: siteurl + "index.php/student/test",
options: [{
text: 'Bye and thank you.',
next: 3
},
{
text: 'I would like some more info please.',
next: 104
}]
},
103: {
text: 'Info here',
options: [{
text: 'That is great, thanks!',
next: 103
},
{
text: 'I would like some more info please.',
next: 104
}]
},
5: {
text: 'Aah, you\'re missing out!',
next: 6
},
6: {
text: 'You should check it out on test preview',
options: [{
text: "Go to PRIVIEW",
url: "www.google.com"
}]
},
};
const bot = function () {
const peekobot = document.getElementById('peekobot');
const container = document.getElementById('peekobot-container');
const inner = document.getElementById('peekobot-inner');
let restartButton = null;
const sleep = function (ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
const scrollContainer = function () {
inner.scrollTop = inner.scrollHeight;
};
const insertNewChatItem = function (elem) {
//container.insertBefore(elem, peekobot);
peekobot.appendChild(elem);
scrollContainer();
//debugger;
elem.classList.add('activated');
};
const printResponse = async function (step) {
const response = document.createElement('div');
response.classList.add('chat-response');
response.innerHTML = step.text;
insertNewChatItem(response);
await sleep(1500);
if (step.options) {
const choices = document.createElement('div');
choices.classList.add('choices');
step.options.forEach(function (option) {
const button = document.createElement(option.url ? 'a' : 'button');
button.classList.add('choice');
button.innerHTML = option.text;
if (option.url) {
button.href = option.url;
} else {
button.dataset.next = option.next;
}
choices.appendChild(button);
});
insertNewChatItem(choices);
} else if (step.next) {
printResponse(chat[step.next]);
}
};
const printChoice = function (choice) {
const choiceElem = document.createElement('div');
choiceElem.classList.add('chat-ask');
choiceElem.innerHTML = choice.innerHTML;
insertNewChatItem(choiceElem);
};
const disableAllChoices = function () {
const choices = document.querySelectorAll('.choice');
choices.forEach(function (choice) {
choice.disabled = 'disabled';
});
return;
};
const handleChoice = async function (e) {
if (!e.target.classList.contains('choice') || 'A' === e.target.tagName) {
// Target isn't a button, but could be a child of a button.
var button = e.target.closest('#peekobot-container .choice');
if (button !== null) {
button.click();
}
return;
}
e.preventDefault();
const choice = e.target;
disableAllChoices();
printChoice(choice);
scrollContainer();
await sleep(1500);
if (choice.dataset.next) {
printResponse(chat[choice.dataset.next]);
}
// Need to disable buttons here to prevent multiple choices
};
const handleRestart = function () {
startConversation();
}
const startConversation = function () {
printResponse(chat[1]);
}
const init = function () {
container.addEventListener('click', handleChoice);
restartButton = document.createElement('button');
restartButton.innerText = "Restart";
restartButton.classList.add('restart');
restartButton.addEventListener('click', handleRestart);
container.appendChild(restartButton);
startConversation();
};
init();
}
bot();
</script>
UPDATE:
Based on a suggestion below, I've tried:
if (step.options) {
const choices = document.createElement('div');
choices.classList.add('choices');
step.options.forEach(function (option) {
const button = document.createElement(option.url ? 'a' : 'button');
button.classList.add('choice');
button.innerHTML = option.text;
if (option.url) {
button.href = option.url;
if (option.target) {
button.target = option.target;
} else {
button.dataset.next = option.next;
}
choices.appendChild(button);
});
insertNewChatItem(choices);
} else if (step.next) {
printResponse(chat[step.next]);
}
};
This breaks the whole code so the chatbot doesn't run at all.
What I'm thinking is you would have to modify the code or get the author to modify the code for you.
I'm looking at the main js code here: https://github.com/Peekobot/peekobot/blob/master/peekobot.js
This snippet is what I am looking at:
step.options.forEach(function (option) {
const button = document.createElement(option.url ? 'a' : 'button');
button.classList.add('choice');
button.innerHTML = option.text;
if (option.url) {
button.href = option.url;
} else {
button.dataset.next = option.next;
}
choices.appendChild(button);
});
That last part would get changed to something like this, I would think.
if (option.url) {
button.href = option.url;
if (option.target) {
button.target = option.target;
}
} else {
...

Javascript - checkbox reading value from array of objects

I need to get my checkboxes in todo list read from object in array if task is complete or not. And stay checked when they're complete and unchecked or not.
const todos = [
{
id: 1,
title: 'Nakupit',
description: 'Mlieko, syr, vajcia',
completed: false,
},
{
id: 2,
title: 'Umyt auto',
description: '+ povysavat',
completed: true,
},
]
This is the array and i tried on checkbox something like this. And variations, unable to get it work and changing array or value from array making checkbox ,checked, if complete
toDoCheckbox.addEventListener('click', () => {
if (toDoCheckbox.checked = true) {
return todos.completed === true
} else {
return todos.completed === false
}
});
Can anyone help with it?
You can modify the todos state by looking it up by its id and flipping its completed status.
I added the button to request re-rendering. This will re-build the checkboxes using the current state.
const todos = [{
id: 1,
title: 'Nakupit',
description: 'Mlieko, syr, vajcia',
completed: false,
}, {
id: 2,
title: 'Umyt auto',
description: '+ povysavat',
completed: true,
}]
const main = () => {
const btn = document.querySelector('.btn-render')
btn.addEventListener('click', reRender)
renderTodos()
}
const reRender = (e) => {
renderTodos()
}
const onCheck = (e) => {
const id = parseInt(e.currentTarget.dataset.id, 10)
const found = todos.find(todo => todo.id === id)
if (found) {
found.completed = !found.completed // Flip status
}
}
const renderTodos = () => {
const container = document.querySelector('.container')
container.innerHTML = '' // Clear
todos.forEach(todo => {
let wrapper = document.createElement('div')
let label = document.createElement('label')
let checkbox = document.createElement('input')
wrapper.classList.add('checkbox-wrapper')
label.textContent = todo.title
checkbox.type = 'checkbox'
checkbox.checked = todo.completed
checkbox.dataset.id = todo.id
checkbox.addEventListener('change', onCheck)
wrapper.appendChild(label)
wrapper.appendChild(checkbox)
container.appendChild(wrapper)
})
}
main()
.checkbox-wrapper label {
display: inline-block;
width: 6em;
}
<div class="container"></div>
<br />
<button class="btn-render">Re-render</button>

Check if userid matches one in the array

I'm attempting to check if a user's ID is in this array and if they are, also get the "text" from it.
Array:
const staff = [
{
user: '245569534218469376',
text: 'dev'
},
{
user: '294597887919128576',
text: 'loner'
}
];
I've tried if (staff.user.includes(msg.member.id)) (Which I didn't think was going to work, and didn't.)
const findUser = (users, id) => users.find(user => user.id === id)
const usersExample = [
{
id: '123456765',
text: 'sdfsdfsdsd'
},
{
id: '654345676',
text: 'fdgdgdg'
}
]
//////////////////
const user = findUser(usersExample, '123456765')
console.log(user && user.text)
The some method on an array is used to tell if an item meets a condition, it is similar to the find method but the find method returns the item where the some method return true or false.
const staff = [
{
user: '245569534218469376',
text: 'dev'
},
{
user: '294597887919128576',
text: 'loner'
}
];
const isStaff = (staff, id) => staff.some(s => s.user === id);
console.log(isStaff(staff, '123'));
console.log(isStaff(staff, '245569534218469376'));
You may try something like this:
const staff = [
{
user: '245569534218469376',
text: 'dev'
},
{
user: '294597887919128576',
text: 'loner'
}
];
let item = staff.find(item => item.user == '294597887919128576'); // msg.member.id
if (item) {
console.log(item.text);
}
One another way to do that is:
const inArray = (array, id) => array.filter(item => item.user === id).length >= 1;
const users = [
{
user: '245569534218469356',
text: 'foo'
}, {
user: '245564734218469376',
text: 'bar'
}, {
user: '246869534218469376',
text: 'baz'
}
];
console.log(inArray(users, '246869534218469376')); // true
console.log(inArray(users, '222479534218469376')); // false

Showing an image depending on what option you choose

I was following a tutorial (https://www.youtube.com/watch?v=R1S_NhKkvGA) on how to make a text adventure game in javascript, and wanted to re purpose that code into a game of my own, except depending on what option you choose, the image changes. How would I do that? Original code can be found here (https://codepen.io/WebDevSimplified/pen/xoKZbd), but heres the basic gist of the code:
const textElement = document.getElementById('text')
const optionButtonsElement = document.getElementById('option-buttons')
let state = {}
function startGame() {
state = {}
showTextNode(1)
}
function showTextNode(textNodeIndex) {
const textNode = textNodes.find(textNode => textNode.id === textNodeIndex)
textElement.innerText = textNode.text
while (optionButtonsElement.firstChild) {
optionButtonsElement.removeChild(optionButtonsElement.firstChild)
}
textNode.options.forEach(option => {
if (showOption(option)) {
const button = document.createElement('button')
button.innerText = option.text
button.classList.add('btn')
button.addEventListener('click', () => selectOption(option))
optionButtonsElement.appendChild(button)
}
})
}
function showOption(option) {
return option.requiredState == null || option.requiredState(state)
}
function selectOption(option) {
const nextTextNodeId = option.nextText
if (nextTextNodeId <= 0) {
return startGame()
}
state = Object.assign(state, option.setState)
showTextNode(nextTextNodeId)
}
const textNodes = [
{
id: 1,
text: 'You wake up in a strange place and you see a jar of blue goo near you.',
options: [
{
text: 'Take the goo',
setState: { blueGoo: true },
nextText: 2
},
{
text: 'Leave the goo',
nextText: 2
}
]
}
My changes are just the text on the buttons and the story text.
You can try this in the selectOption function:
function selectOption(option) {
const nextTextNodeId = option.nextText
switch (nextTextNodeId) {
case 2:
document.body.style.backgroundImage = "url('https://images.pexels.com/photos/2886284/pexels-photo-2886284.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260')";
break;
}
if (nextTextNodeId <= 0) {
return startGame()
}
state = Object.assign(state, option.setState)
showTextNode(nextTextNodeId)
}
Now you just have to add new cases with your preferred image.

Categories