Remove elements from array on click in js? - javascript

I have an interface [ref below image] where whenever user clicked on close button Image needs to disappear from interface as well as from the array as images are stored in array but currently it is disappeared from front-end interface not from the array.
How will I delete element from interface and removed from array together ?
//a list of img urls needed to feed the list of pictures with some
const images = ['https://www.w3schools.com/css/img_5terre.jpg', 'https://www.w3schools.com/css/img_forest.jpg', 'https://www.w3schools.com/css/img_lights.jpg', 'https://www.w3schools.com/css/img_mountains.jpg'];
//inits the imageList with pictures coming from images constant
//..so when the document is ready,
$(document).ready(() => {
//for each url picture in the images constant
images.forEach((o, i) => {
//append a picture to imageList having that url
appendImageToList(o);
});
});
//appends an image to the list (where image is a picture url)
function appendImageToList(image) {
var img = document.createElement("img");
img.src = image;
//the img will be enclosed in a container
let container = $('<div>', {
class: 'imgContainer'
});
container.append(img);
//creates the close handle for this new picture and adds an event handler on its click event
let closeHandle = $('<div>', {
class: 'closeHandle'
});
//adds content x to the close handle
closeHandle.append('<i class="fa-solid fa-circle-xmark"></i>');
closeHandle.click(() => {
//when the button is clicked, remove this image from the list
removeOneImageFromList($(event.target).closest('.imgContainer'));
});
container.append(closeHandle);
//adds the container inside the imageList
$('#imageList').prepend(container);
}
//removes all images from the list
function removeAllImages() {
$('#imageList .imgContainer').remove();
}
//removes a specific image from the list
function removeOneImageFromList(imgParentElement) {
$(imgParentElement).remove();
}
/* rule to style every single img container */
#imageList .imgContainer {
position: relative;
width: fit-content;
}
/* rule to style the close handle */
#imageList .closeHandle {
position: absolute;
top: 15px;
right: 15px;
text-align: center;
cursor: pointer;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.0.slim.js"></script>
<button type="button" onclick="removeAllImages();">Remove all images</button>
<br><br>
<div id="imageList">
</div>

Modify your removeOneImageFromList to also use Array.splice to remove the element that matches the src of the image contained in the container that is being removed. You should also modify removeAllImages to empty the list.
//removes all images from the list
function removeAllImages() {
images = []
$('#imageList .imgContainer').remove();
}
//removes a specific image from the list
function removeOneImageFromList(imgParentElement) {
images.splice(images.indexOf($(imgParentElement).find('img').attr('src')), 1)
$(imgParentElement).remove();
}

Related

Scroll function to navigate to appropriate section using JavaScript

My goal is to complete a dynamic single landing page using JavaScript. HTML and CSS files were already provided and I managed to build an unordered list by manipulating the DOM.
The thing that got me stuck is: When clicking an item from the navigation menu, the link should scroll to the appropriate section.
I cannot get this to work :/
Below is the JS code so far.
/* Declare variables for the fictive document and menu list to retrieve and store variables as unordered list */
const container = document.createDocumentFragment();
const menuList = document.getElementsByTagName('section');
/* Function to create the navigation menu as a clickable link */
function navigationLink(id, name) {
const navLink = `<a class = "menu__link" data-id=${id}">${name}</a>`;
return navLink;
}
/* Function for the navigation list, built as an unordered list */
function createNavigation() {
for (let i = 0; i < menuList.length; i++) {
const newMenuListItem = document.createElement('li');
const menuListName = menuList[i].getAttribute('data-nav')
const menuListID = menuList[i].getAttribute('id')
newMenuListItem.innerHTML = navigationLink(menuListID, menuListName)
container.appendChild(newMenuListItem);
}
/* Retrieve the id from the ul section to be added to the document fragment: container */
const navBarMenu = document.getElementById('navbar__list')
navBarMenu.appendChild(container);
}
// Add class 'active' to section when near the top of viewport
function setActiveClass() {
for (let i = 0; i < menuList.length; i++) {
if (isInViewport(menuList[i])) {
menuList[i].classList.add("your-active-class");
} else {
menuList[i].classList.remove("your-active-class");
}
}
}
To solve the problem I looked into another piece of code that I haven't been able to function properly.
function scrollToElement(event) {
if (event.target.nodeName === 'A') {
const menuListID = event.target.getAttribute('data-id');
const menu = document.getElementById(menuListID);
menu.scrollIntoView({ behavior: "smooth" });
}
}
document.addEventListener('scroll', function () {
setActiveClass();
});
const navBarMenu = document.getElementById('navbar__list')
navBarMenu.addEventListener('click', function (event) {
scrollToElement(event)
})
You can use anchor to do this. This will make your life easier than trying to do this in js.
To use it, you just have to set an id to a node. Like <div id="myFirstId"></div> and then, set the href of your link to #myFirstId.
If you want your scroll to not be instant, you can add the scroll-behavior: smooth to the scrollable element.
html {
scroll-behavior: smooth;
}
#scrollable {
overflow:auto;
}
#firstDiv {
background-color: green;
height: 700px
}
#secondDiv {
background-color: yellow;
height: 200px;
}
#thirdDiv {
background-color: blue;
height: 500px;
}
firstDiv
SecondDiv
thirdDiv
<div id="scrollable">
<div id="firstDiv"></div>
<div id="secondDiv"></div>
<div id="thirdDiv"></div>
</div>
For your code, just change this line <a class = "menu__link" data-id=${id}">${name}</a>
To this <a class="menu__link" href="#${id}">${name}</a>

JS: addClass on click and removeClass on window click (or outside of div)

I am trying to add a classList to an element "onclick", then remove that classList after any click.
const images = document.querySelectorAll(".section-projects-img");
images.forEach(img => {
img.addEventListener("click", () => {
img.classList.toggle("changed");
});
});
this adds/removes when I click on the image, but I want it removed no matter where I click so that more than one img can't have same classList. I've tried adding classList to the element then removing by using classList.contains(). This seems super simple and quite embarrassing I can't figure it out. Thank you in advanced!
Because events bubble up from the event target right up to the top level element, we can do something like this:
// just one event listener
document.body.addEventListener('click', clickEventDispatch, false);
function clickEventDispatch (e) {
var _evt = (e.target || this);
console.log(_evt.nodeName, _evt.className);
/*
in practice you might want to change the criteria,
and indeed possibly use if/else if instead of switch,
but this will do for the purposes of demonstration:
*/
switch (_evt.nodeName) {
// it's an image
case "IMG":
/*
EDIT:
Didn't realise you wanted to toggle if active image clicked
This oughta do it
*/
if (_evt.classList.contains('changed')) {
_evt.classList.remove('changed')
}
else {
resetChangedImages();
_evt.classList.add('changed')
};
break;
// it's not an image
default:
resetChangedImages();
}
}
function resetChangedImages () {
/*
[].slice.call here just converts the NodeList we get
from querySelectorAll into an Array we can iterate over
with forEach, rather than having to use for loop
*/
[].slice.call(document.querySelectorAll('img')).forEach(function (img) {
img.classList.remove('changed');
});
}
.container {
padding: 32px 16px 200px;
background: #d1d1d1;
}
.section-projects-img {
border: 2px solid #fff;
}
.changed {
border-color: red;
}
<div class="container">
<img class="section-projects-img" src="https://i.imgur.com/2BLYrIxs.jpg" />
<img class="section-projects-img" src="https://i.imgur.com/PIdF6ANs.jpg" />
<img class="section-projects-img" src="https://i.imgur.com/uVm9rTfs.jpg" />
<img class="section-projects-img" src="https://i.imgur.com/6x0UzSVs.jpg" />
<img class="section-projects-img" src="https://i.imgur.com/mvqzSFMs.jpg" />
</div>
This means we're not adding an event listener for every image, just one on the body, and then if you need event listeners for other elements like buttons etc. you can just include them in your event dispatcher function.
If your objective is only to prevent more than one image from having the changed class, simply loop through all images and remove the class in the click event listener.
Do remember to first check whether the image being looped through is the image clicked however, otherwise the class will never be removed (the class is removed but toggled back again):
const images = document.querySelectorAll(".section-projects-img");
images.forEach(img => {
img.addEventListener("click", () => {
images.forEach(e => e != img ? e.classList.remove("changed") : '');
img.classList.toggle("changed");
});
});
.changed {
filter: brightness(2);
}
<img class="section-projects-img" src="https://www.gravatar.com/avatar/0fdacb141bca7fa57c392b5f03872176?s=48&d=identicon&r=PG&f=1" />
<img class="section-projects-img" src="https://www.gravatar.com/avatar/0fdacb141bca7fa57c392b5f03872176?s=48&d=identicon&r=PG&f=1" />
<img class="section-projects-img" src="https://www.gravatar.com/avatar/0fdacb141bca7fa57c392b5f03872176?s=48&d=identicon&r=PG&f=1" />

Add task validation still adds a empty task even though field is blank

In a tutorial i am following the tutor uses an alert box to validate an empty text field on clicking "Add Task", if there is no task the user gets an alert to tell them to add to the task. I wanted to modify this abit and instead of an alert I wanted to have a message appear under the text field which I have done successfully, however I have noticed that this still adds an blank task to the "Tasks" section of my app.
I have tried to call the removeTask() function so that it removes the blank task, but this didn't work and also I feel this is a hackey way to solve this.
I have tried to add an else statement to the if statement that presents the message and add in the code that would populate the "Tasks" section, but this didn't work too
Not sure what else to try?
// ADD TASK FUNCTION
// addTask Function Creation
function addTask(e) {
if(taskInput.value === '') {
//alert('Add a Task');
const addTaskErr = document.getElementById('addTaskError');
addTaskErr.style.color = 'red';
addTaskErr.innerHTML = 'Please add a task';
} else {
// Creat li Element
const li = document.createElement('li');
// Add class
li.className = 'collection-item';
// Create Text Node and Append to li
li.appendChild(document.createTextNode(taskInput.value));
// Create new link element
const link = document.createElement('a');
// Add Class
link.className = 'delete-item secondary-content';
// Add Icon HTML
link.innerHTML = '<i class="fa fa-remove"></i>'
// Append Link To li
li.appendChild(link);
// Append li to ul
taskList.appendChild(li);
//console.log(li);
// Store task in Local Storage
storeTaskInLocalStorage(taskInput.value);
// Clear The Input
taskInput.value = '';
}
e.preventDefault();
}
Expected Result:
It should only present a message if there is no task typed in the new task text field and if there is, it should not present a message and add the task.
Actual Results:
Even if the new task text field is left empty a new blank task is still being added to the "Task" section of my app on clicking "Add Task".
Please can you guide me on how this would be done and if I need to provide any further information or code?
Thanks
Nav
After reading your question and javascript code, I understood that you're calling function addTask with some text when add button is clicked. So, I prepared sample HTML based upon my understanding. It is similar to the TODO list application. You can run the below snippet.
Why you are getting issue
Because you're always checking value is initial or not. If value initial then you are directly showing message and also not removing it when task added.
Below changes I did
Moved error text to the HTML code and read the element into one global variable addTaskErr. Initially error text set to hidden and when value is empty then I'm showing this text by setting display to empty.
addTaskErr.style.display = '';
When new task is added as a initial step I'm hiding the error text
addTaskErr.style.display = 'none';
While preparing the list element I'm adding event listener to the delete button to delete the list and when it clicked. Removing the list element by getting parent element by using this.parentElement property.
`link.addEventListener('click', function(e){
this.parentElement.remove();
e.preventDefault();
});`
Sample Code
var taskInput = document.getElementById('taskInput'),
taskList = document.getElementById('taskList'),
addTaskErr = document.getElementById('addTaskError');
//_localStorage = localStorage,
//counter = 0;
function addTask(e) {
if(taskInput.value === '') {
addTaskErr.style.display = '';
} else {
addTaskErr.style.display = 'none';
// Creat li Element
const li = document.createElement('li');
// Add class
li.className = 'collection-item';
// Create Text Node and Append to li
li.appendChild(document.createTextNode(taskInput.value));
// Create new link element
const link = document.createElement('a');
// Add Class
link.className = 'delete-item secondary-content';
// Add Icon HTML
link.innerHTML = '<span class="fa fa-remove">X</span>'
// Append Link To li
li.appendChild(link);
link.addEventListener('click', function(e){
this.parentElement.remove();
//counter--;
e.preventDefault();
});
//counter++;
// Append li to ul
taskList.appendChild(li);
//console.log(li);
// Store task in Local Storage
storeTaskInLocalStorage(taskInput.value);
// Clear The Input
taskInput.value = '';
}
e.preventDefault();
}
function storeTaskInLocalStorage(value){
/*_localStorage.setItem('tasks', JSON.stringify(taskLists));*/
}
.fa.fa-remove{
padding:4px 6px;
border-radius:50%;
background: #f2f2f2;
color:red;
margin-left:5px;
float: right;
position: absolute;
right: 0;
}
li{
height:30px;
margin-bottom: 5px;
border:1px solid #f5f5f5;
position: relative;
width: 300px;
}
<div>
<input type="text" id="taskInput"/>
<button onclick="addTask(event)">Add</button>
</div>
<div>
<span id="addTaskError" style="color:red; display:none;">
Please enter a task
</span>
</div>
<div>
<ol id="taskList">
</ol>
</div>

className on children affects parent element

Im building a card object and I want it to have the className .card which is defined in css like this:
.card img{position:absolute; width:150px; height:160px}
which mean I want only the images inside my div to cover each other and not the div's themselves. Every time a create 2 or more cards, they cover each other as if they had the position: absolute property.
My JavaScript Code:
this.add = function()
{
console.log("add");
this.container.appendChild(this.backImg);
this.container.appendChild(this.frontImg);
this.container.className = "card";
document.body.appendChild(this.container);
};
};
var card1 = new Card(1);
card1.add();
Apply the same width and height of the images to the container card, that way:
.card { position: relative; width:150px; height:160px }

How to add an element between 2 flex-styled rows and make the row below move down based on new element height?

Please take a look at this basic example:
http://tympanus.net/Blueprints/ResponsiveFullWidthGrid/
Now imagine that by clicking a cat box, I would need (especially on small to medium screens) to add a 100%-width text box (say a description of the clicked cat) below the clicked cat's row. That text box should push down the rest of the rows.
I am full css/js/frontend developer but I never faced a problem like this. It's also the first time I'm going to use a flexbox layout. With a fixed layout would be quite trivial, but in this case I cannot figure out a good way of doing it. One of the things to solve for example is: where should I put the box (relative to the clicked box?), and should I change position via javascript based on current items-per-row or maybe there a smarter css way?
Any idea is appreciated.
That was an interesting challenge :)
The only way to know where to place the expanded area (I called it infoBox), is to identify the 1st node of the next line, and then insert it before it. If there is no node on the last line, we can append it to the end of the ul.
I've also added a window.resize event handler that will close the infoBox, so it won't break the responsive layout, and a close button.
Working example - fiddle.
HTML was copy paste from the codrop article.
JS
var rfgrid = document.querySelector('.cbp-rfgrid');
var items = Array.from(document.querySelectorAll('.cbp-rfgrid > li'));
/** Create infoBox **/
var infoBox = document.createElement('div');
infoBox.classList.add('infoBox');
infoBox.innerHTML = '<div class="close">X</div><div class="content"></div>';
infoBoxClose = infoBox.querySelector('.close');
infoBoxContent = infoBox.querySelector('.content');
/** add close button functionality **/
infoBoxClose.addEventListener('click', function() {
rfgrid.removeChild(infoBox);
});
/** remove infoBox on resize to maintain layout flow **/
window.addEventListener('resize', function() {
rfgrid.removeChild(infoBox);
});
items.forEach(function (item) {
item.addEventListener('click', function (event) {
event.preventDefault();
var insertReference = findReference(this); // get refence to next line 1st node
infoBoxContent.innerHTML = items.indexOf(this); // example of changing infoBox content
if(insertReference) {
rfgrid.insertBefore(infoBox, insertReference); // insert infoBox before the reference
} else {
rfgrid.appendChild(infoBox); // insert infoBox as last child
};
});
});
/** find reference to 1st item of next line or null if last line **/
function findReference(currentNode) {
var originalTop = currentNode.offsetTop; // get the clicked item offsetTop
do {
currentNode = currentNode.nextSibling; // get next sibling
} while (currentNode !== null && (currentNode.nodeType !== 1 || currentNode.offsetTop === originalTop)); // keep iterating until null (last line) or a node with a different offsetTop (next line)
return currentNode;
}
CSS (in addition to the original)
.infoBox {
position: relative;
width: 100%;
height: 200px;
padding: 20px 0 0 0;
clear: both;
background: paleturquoise;
}
.infoBox > .close {
position: absolute;
top: 5px;
right: 5px;
cursor: pointer;
}

Categories