Can we use vue splice inside foreach() to delete multiple elements? - javascript

I have a image grid(in array form),
There I am trying to add multi-deletion functionality.
So When checkbox of each image from grid is checked, I am maintaining an array named selectedImages with selected image's Index from imagegrid array
and on click of delete button I have coded below remove method:
removeSelectedImages() {
console.log(this.additionalPhotos); //ImageGrid array
console.log(this.selectedImages); //selectedImages array
this.selectedImages.sort().reverse();
this.selectedImages.forEach((selectedImageIndex, index) => {
this.additionalPhotos.splice(selectedImageIndex, 1);
this.selectedImages.splice(index, 1);
});
},
But issue I am facing is splice is just working once, initially I thought that is because suppose I reset value of index 2 and when it comes to index 3 then it becomes index 2 so issue is coming. so as solution I have reversed selected images array based in value (indexes of Grid image array) but still it is behaving same.
suppose I selected image 2 & 1 but splice only deleting value 2 second time it is not coming inside loop however if I remove splice then it runs 2 times.
Kindly suggest best method for it.

You can try to filter deleted images:
let additionalPhotos = [{fileSrc: 'aa', fileName: '1.png'}, {fileSrc: 'bb', fileName: '2.png'}, {fileSrc: 'cc', fileName: '3.png'}, {fileSrc: 'dd', fileName: '4.png'}, {fileSrc: 'ee', fileName: '5.png'}, {fileSrc: 'ff', fileName: '6.png'},]
const selectedImages = [2,4]
function removeSelectedImages() {
additionalPhotos = additionalPhotos.filter((_, idx) => {
return !selectedImages.includes(idx)
})
}
removeSelectedImages()
console.log(additionalPhotos)

removeSelectedImages() {
console.log(this.additionalPhotos); //ImageGrid array
console.log(this.selectedImages); //selectedImages array
this.selectedImages.sort()
let c
for (c=selectedImages.length-1; c>=0; c--) {
this.additionalPhotos.splice(selectedImages[c],1)
}
this.selectedImages=[]
}

Related

Slice An Array Of Files In A Change Event Listener, When The Total Allowed Number Of Files Is Added In More Than One Go - JavaScript

I have a form that takes file uploads and it currently has a limit of 10 files per upload. There are PHP validations in the backend for this too.
When more than 10 files are attached, I currently have a JavaScript slice(0, 10) method inside a change event for the file input element, which removes any files (and their preview image thumbnails) when the number attached is more than 10 files.
// For each added file, add it to submitData (the DataTransfer Object), if not already present
[...e.target.files].slice(0,10).forEach((file) => {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
});
The Issue
What I can’t seem to do though is work out a way to slice() the files array in a compound attachment situation, i.e. if 8 files are attached initially, and then the user decides to add another 4 prior to submitting the form, taking the total to 12. The current slice only happens when more than 10 are added in one go.
I have a decode() method that runs inside a loop (for every image attached) that carries out frontend validations, and a promiseAllSettled() method that waits for the last image to be attached prior to outputting a main error message telling the user to check the specific errors on the page.
Question
How do I slice the array on the total number of files appended, if the user has initially attached a file count less than 10, then attaches further files taking it more than 10 prior to form submission?
const attachFiles = document.getElementById('attach-files'), // file input element
dropZone = document.getElementById('dropzone'),
submitData = new DataTransfer();
dropZone.addEventListener('click', () => {
// assigns the dropzone to the hidden 'files' input element/file picker
attachFiles.click();
});
attachFiles.addEventListener('change', (e) => {
const currentSubmitData = Array.from(submitData.files);
console.log(e.target.files.length);
// For each added file, add it to 'submitData' if not already present (maximum of 10 files with slice(0, 10)
[...e.target.files].slice(0,10).forEach((file) => {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
});
// Sync attachFiles FileList with submitData FileList
attachFiles.files = submitData.files;
// Clear the previewWrapper before generating new previews
previewWrapper.replaceChildren();
// the 'decode()' function inside the 'showFiles()' function is returned
// we wait for all of the promises for each image to settle
Promise.allSettled([...submitData.files].map(showFiles)).then((results) => {
// output main error message at top of page alerting user to error messages attached to images
});
}); // end of 'change' event listener
function showFiles(file) {
// code to generate image previews and append them to the 'previewWrapper'
// then use the decode() method that returns a promise and do JS validations on the preview images
return previewImage.decode().then(() => {
// preform JS validations and append
}).catch((error) => {
console.log(error)
});
} // end of showfiles(file)
Instead of looking into your whole code, Here I came up with the solution/Suggestion as per looking into a specific piece of code.
Once you spliced the initial set of files, After that you can check how many files are remaining and then you can pass the second parameter in the splice dynamically based on the required number of files.
Steps :
Create a separate empty array and insert first selection of files into that array on change event.
Now check for this newly created array length, if there is any number of elements then you can update the 2nd splice method parameter like this :
secondParameter = secondParameter - newArr.length
Live Demo :
let splicedArrCount = 10;
let resArr = [];
function spliceArr() {
const inputVal = Number(document.getElementById('number').value);
if (inputVal) {
const arr = Array(inputVal).fill(inputVal);
if (!resArr.length) {
resArr = [...arr];
} else if (resArr.length) {
splicedArrCount = splicedArrCount - resArr.length
resArr.push(...arr.splice(0, splicedArrCount));
}
}
console.log(resArr);
}
<input type="number" id="number" onChange="spliceArr()"/>
In the above demo, You can do a testing by inserting a number into a input box, which will convert the number into an array elements. For ex. If you will enter 8, it will create an array with 8 elements and then if you pass 4, it will update an array. i.e. [8, 8, 8, 8, 8, 8, 8, 8, 4, 4]
We can keep the count of the number of files already added.
Instead of [...e.target.files].slice(0,10).forEach((file) => {}, we can do something like:
var numberOfFilesToAdd = 10 - currentSubmitData.length;
[...e.target.files].slice(0,numberOfFilesToAdd).forEach((file) => {}
Keep an array (below, called allSelectedFiles) of all of the files selected so far, and keep adding to that array as user selects more.
Keep another array (below, called filesForUpload) that's a subset of the first array, filtered for uniqueness and sliced to the max length. Present this subset array in the DOM to give user feedback, and use it to drive the actual upload on submit.
let allSelectedFiles = [];
let filesForUpload = [];
const totalAllowed = 4; // 10 for the OP, but 3 is simpler to demo
const attachFiles = document.getElementById('attach-files');
attachFiles.addEventListener('change', e => {
allSelectedFiles = [... allSelectedFiles, ...e.target.files];
let filenames = new Set();
filesForUpload = allSelectedFiles.filter(f => {
let has = filenames.has(f.name);
filenames.add(f.name);
return !has;
});
filesForUpload = filesForUpload.slice(0, totalAllowed);
showFiles(filesForUpload);
});
// unlike the OP, just to demo: fill a <ul> with filename <li>'s
function showFiles(array) {
let list = document.getElementById('file-list');
while(list.firstChild) {
list.removeChild( list.firstChild );
}
for (let file of array) {
let item = document.createElement('li');
item.appendChild(document.createTextNode(file.name));
list.appendChild(item);
}
}
// on form submit, not called in the above, trim the selected files
// using the same approach: someSelectedFiles()
function submit() {
/*
let submitData = new DataTransfer();
for (let file of filesForUpload) {
submitData.add(file)
}
// and so on
*/
}
<h3>Input some files</h3>
<p>To see the logic, choose > 4 files, choose some overlapping names</p>
<input type="file" id="attach-files" multiple />
<ul id="file-list"></ul>
Slicing before checking duplicates can possibly drop valid values.
I would go for something like this:
for (const file of e.target.files) {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
if (submitData.items.size >= 10) break; // assuming submitData.items is a Set
}

How to push item in specific index of and array of useState reactJs [duplicate]

I'm attempting to add an object at a specific point in my 'data' array which is this components state. The following isn't working, the array simply gets emptied.
addNewBulletAfterActive = () => {
const array = this.state.data;
const newBulletPoint = {
id: this.state.data.length += 1,
title: 'Click to add'
};
const newData = array.splice(this.state.activeBulletPointId, 0, newBulletPoint);
this.setState({
data: newData
});
}
The idea is that if I have a list of 10 bullet points, the user can click on the 4th bullet point and press enter to add a new bullet point directly after. I've not had any issues adding items to the end of the array but it looks like .splice is causing issues.
I believe this should do what you're after.
function addAfter(array, index, newItem) {
return [
...array.slice(0, index),
newItem,
...array.slice(index)
];
}
This function returns a new array with a new item inserted in the middle. It doesn't mutate your original array and so will play nicely with component's state and Redux.
You can then assign the output from this function to your state.
splice returns spliced items (which is empty since you splice 0 items) and mutates original array.
const newData = array.slice(0); // copy
newData.splice(this.state.activeBulletPointId, 0, newBulletPoint);
this.setState({
data: newData
});
I think this could be an easier and faster method to do this
/*Just plain JS*/
function AddAfter(array, newObject){
array.unshift(newObject);
}
/*In react If updating state*/
var _prev = this.state.your_array; //getting the current value for the state object
var newInfo = {id: 1, more: 'This is a new object'};
_prev.unshift(newInfo);

Reducer is adding items in the store out of nowhere

So I am trying to add a cart feauture to my React-redux site and I got stuck on a very weird occurance. So this is what I get from the payload of the action for example:
{
info: 'Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops',
id: 1,
price: 109.95,
image: 'https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg',
count: 5,
totalPrice: 549.75
}
So what Im trying to do is, when an item with the same id as this one is trying to be added, to not add it, but to increase the count of the item with the same id that already exists in the cart:
const index = state.currentCart.findIndex((x) => x.id === id);
return {
...state,
currentCart: [
...state.currentCart,
state.currentCart[index].count += 1,
(state.currentCart[index].totalPrice =
state.currentCart[index].price * state.currentCart[index].count),
],
};
The count itself is increased, but there is something really strange happening at the same time.
The total price of the product and its count are also added as elements of the currentCart array, when the only thing that should happen is to update the count of the cart item with the id from the payload,
this is what happens to the currentCart array when this action is fired:
currentCart: [
{
info: 'Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops',
id: 1,
price: 109.95,
image: 'https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg',
count: 6,
totalPrice: 659.7
},
2,
219.9,
3,
329.85,
4,
439.8,
5,
549.75,
6,
659.7
]
}
I am sure I am not mutating the state right, thank you in advance!
No, they are not coming from nowhere, you are actively adding the values to the array.
You seem to be a bit confused about how to properly handle states. You either choose an immutable approach (which I really recommend if you are using react) or you choose to mutate your references.
In javascript, when you do an assignment, that assignment also returns the value that is being assigned, so for example here:
let x = 1
let b = x+=1
// b is now 2 and x is 2
let c = b += 2
// b is now 4 and c is also 4
That is exactly what is happening on your array assignment. You are first spreading the old version of the array on the new one (making a copy) and then you mutate the reference to the current car at the same time (and this is the key part) that you are saving the return value of those assignments in the array itself.
Take a look at the values on the array, they are the results of your operations:
count (1) += 1 // 2
price (109.95) * count (2) = 219.9,
count (2) += 1 // 3
price (109.95) * count (3) = 329.85
... etc
So what you have on your array is an historic of the count and total price values.
This is a breakdown of what is happening in your code:
// Will allways be at index 0, because you add it as first element
// and then you keep copying the array below
const index = state.currentCart.findIndex((x) => x.id === id);
return {
...state,
currentCart: [
// Here you are copying the old array into the new one,
// keeping the current car at the first position
...state.currentCart,
// Here you are updating the values of the object at index 0
// and at the same time you are adding those values at
// the end of the array
state.currentCart[index].count += 1,
(state.currentCart[index].totalPrice =
state.currentCart[index].price * state.currentCart[index].count),
],
};
What you want to do is to build a new currentCart each time and. Also you want to use an object for currentCart, not an array. If you want to keep a list of items in the cart, I suggest you tu create a nested property on the cart called items, and make that be an array.
Your code example is not showing us where are you getting the action from, but I will provide you an example assuming you just have it and that the new item to add to the cart comes in the payload.
const currentCart = state.currentCart;
const newItem = action.payload
return {
...state,
currentCart: {
...currentCart,
count: currentCart.count + 1
totalPrice: (newItem.price * newItem.count) + currentCart.totalPrice,
items: [...currentCart.items, newItem]
},
};
I am not sure but this is happening
totalPrice = item's price * no of times the item is added
Other items' price is not getting included. Try this -
state.currentCart[index].totalPrice += state.currentCart[index].price * state.currentCart[index].count
(just '+=' instead of '=')

How to delete an item in a reverse sorted array in *ngFor in Angular

I'm trying to delete a specific item in the array based on the index. Currently, I have an issue deleting the first and last element of the array. When I try delete the last element, the first element gets deleted and vice versa.
Here's an excerpt from my code
HTML
<div *ngFor="let item of itemsList.slice().reverse(); index as i">
<ion-item>{{item.name}} <button (click)="deleteItem(i)">Delete</button></ion-item>
</div>
TS
itemsList = [{
name: 'Item 0'
}];
count = 0;
constructor() {}
addItem() {
this.count += 1
this.itemsList.unshift({
name: `Item ${this.count}`
})
}
deleteItem(index) {
console.log('Delete ', this.itemsList[index].name)
this.itemsList.splice(index, 1)
}
I created a working example using StackBlitz. Could anyone please help?
Use length - index - 1 in your splice to get the correct index of the normal array.
this.itemsList.splice(this.itemsList.length - index -1, 1)
I can't think of any reason why you wouldn't code it like this:
(click)="deleteItem(item)"
It's really a much better way to write code.

javascript compare objects in array

I have a grid of pictures on a page. And, periodically, I want to randomly swap one out for one of 50 I have in an array of Objects- but only if they're not already in the grid. This last part is what my code is failing to do.
I first get all 50 items, and put them into an allmedia array:
// initialize
var allmedia = getAllMedia();
var imagesInGrid = [];
When I place the items in the grid, I add to an array of grid items:
imagesInGrid.push(allmedia [i]); // while looping to fill DOM grid
Then, every 8 seconds I run a getRandomImage() routine that randomly gets an image from the allmedia array and then tests it to see if it's not already in the DOM.
function getRandomImageNotInGrid(){
var randomNumber = Math.floor(Math.random() * allmedia.length);
if (!isInArray(allmedia[randomNumber], imagesInGrid)) {
return allmedia[randomNumber];
} else {
return getRandomImageNotInGrid();
}
}
function isInArray(item, arr) {
if(arr[0]===undefined) return false;
for (var i=arr.length;i--;) {
if (arr[i]===item) {
return true;
}
}
return false;
}
But when I step through the code the (arr[i]===item) test is failing. I can see that the two objects are exactly the same, but the === isn't seeing this as true.
Is this a ByReference / ByValue issue? What am I doing wrong?
console.log:
arr[i]===item
false
arr[i]==item
false
typeof item
"object"
typeof arr[i]
"object"
Edit::
In the output below, I don't understand why arr[0] is not the same as 'item'. I use the exact same object that I put into allmedia as I do when I place the item into the page and, accordingly update imagesInGrid.
console.dir(arr[0]);
Object
caption: Object
comments: Object
created_time: "1305132396"
filter: "Poprocket"
id: "69204668"
images: Object
likes: Object
link: "http://instagr.am/p/EH_q8/"
location: Object
tags: Array[2]
type: "image"
user: Object
__proto__: Object
console.dir(item);
Object
caption: Object
comments: Object
created_time: "1305132396"
filter: "Poprocket"
id: "69204668"
images: Object
likes: Object
link: "http://instagr.am/p/EH_q8/"
location: Object
tags: Array[2]
type: "image"
user: Object
__proto__: Object
Instead of randomly selecting one from allmedia, can you instead remove one from allmedia?
var randomNumber = Math.floor(Math.random() * allmedia.length);
imagesInGrid.push(allmedia.splice(randomNumber,1));
When you use ===, you are comparing the objects by reference. What type of objects are you using to compare? Are you sure they are the same object reference? For example, if you are using strings, you may want to use == instead of ===. If you are using DOM objects, you will want to compare the source, as Alxandr suggested.
Also, your for loop syntax appears to be wrong:
for (var i=arr.length;i--;)
Should be:
for (var i=arr.length - 1; i >= 0; i--)
...if I'm not mistaken.
You don't show any code for how the image "objects" are created or how they are added and removed from the DOM. If you are creating image elements and storing references in an array, then replacing the DOM element with the one from the array, then the comparision should work.
However, if your image object is a bundle of data that is used to create an image element for display, then they will never be equal to each other. Every javascript object is unique, it will only ever be equal to itself.
But I suspect the simplest solution is that suggested by ic3b3rg - remove the object from allMedia when you select it, that way you don't have to test if it's already in imagesInGrid because you can only select each image once. If you want the display to go on forever, then when allmedia is empty, put all the images from imagesInGrid back into it and start again.
Edit
Your problem is the for loop. When you set :
for (var i=arr.length;i--;) {
// On first iteration, i=arr.length
// so arr[i] is undefined
}
i is not decremented until after the first loop, so set i=arr.length-1. It is more common to use while with a decrementing counter:
var i = arr.length;
while (i--) {
// i is decremented after the test
}

Categories