Reputation: 357
So I have an app which displays movies with Movie API and multiple buttons, which show movies of particular genre. So when I click on multiple of such buttons, they all add together in the query, like in the example below:
https://api.themoviedb.org/3/discover/movie?api_key={api_key}&sort_by=popularity.desc&with_genres=99&with_genres=80&with_genres=35
So when I click on one of those genre buttons second time, I want to remove particular genre from url. I tried to do it like this:
function genreChoose(e){
let genres = {
Action: 28,
Adventure: 12,
Animation: 16,
Comedy: 35,
Crime: 80,
Documentary: 99,
Drama: 18,
Family: 10751,
Fantasy: 14,
History: 36,
Horror: 27,
Music: 10402,
Mystery: 9648,
Romance: 10749,
Science_Fiction: 878,
TV_Movie: 10770,
Thriller: 53,
War: 10752,
Western: 37
};
let url = new URL(lastUrl);
if(!e.target.classList.contains('active')){
url.searchParams.append('with_genres', genres[e.target.innerText]);
}else if(e.target.classList.contains('active')){
e.target.classList.remove('active');
url.searchParams.delete('with_genres', genres[e.target.innerText]);
}
fetchURL(url);
console.log(url);
}
So if a button is active and is clicked, it should remove 'active' class and also remove particular genre from the url, but instead, it becomes like this:
https://api.themoviedb.org/3/discover/movie?api_key={api_key}&sort_by=popularity.desc
So instead it just removes all 'with_genres' params, but I'm trying to find a way to delete just one of 'with_genres' parameter.
Edit: so I used T.J. Crowder's example, I added a function to remove value:
let url = new URL(lastUrl);
params = new URLSearchParams(url.search.slice(1));
console.log(params.toString());
if(e.target.classList.contains('active')){
removeValue(params, 'with_genres', genres[e.target.innerText]);
}else if(!e.target.classList.contains('active')){
url.searchParams.append('with_genres', genres[e.target.innerText]);
}
fetchURL(url);
}
But it doesn't really remove anything, maybe I should implement it with promises?
Edit 2: So I added a snippet of code, it doesn't display movie blocks sadly, but you can see how url changes when I click genre buttons. So when I click on it, it adds a genre, but when I unclick it, it doesn't remove anything, tho it shows console.log that genre is removed, so I guess it has to do something with removeValue function, but I can't figure out what.
window.onload = function() {
const API_KEY = 'api_key=73b31f15b44a93f52789c751c34a5d7d';
const BASE_URL = 'https://api.themoviedb.org/3';
const API_URL = '/discover/movie?' + API_KEY + '&sort_by=popularity.desc';
const url = BASE_URL + API_URL;
const movie_container = document.getElementById('movie_container');
const prev = document.getElementById('prev');
const next = document.getElementById('next');
const ulTag = document.querySelectorAll('.numb');
const liTag = document.querySelector('li');
const genres = document.querySelectorAll('.genre');
var lastUrl = '';
fetchURL(url);
trendingMovies = movies =>{
movie_container.innerHTML = '';
movies.forEach(movie => {
img = IMG_URL + movie.poster_path;
title = movie.title;
const movieEl = document.createElement('div');
movieEl.classList.add('movie');
movieEl.innerHTML =
`<img src="${img}">
<span class="movie_title">${title}</span>
`
movie_container.appendChild(movieEl);
});
}
function fetchURL(url) {
lastUrl = url;
console.log(url);
fetch(url)
.then(res => res.json())
.then(data => {
trendingMovies(data.results);
})
}
const removeValue = (params, key, valueToRemove) => {
const values = params.getAll(key);
if (values.length) {
params.delete(key);
console.log('param deleted', params.toString());
for (const value of values) {
if (value !== valueToRemove) {
params.append(key, value);
console.log('params appended:', params.toString());
}
}
}
return params;
};
genres.forEach(el => el.addEventListener('click', (e) => {
genreChoose(e);
if(e.target.classList.contains('active')){
e.target.classList.remove('active');
}else if(!e.target.classList.contains('active')){
e.target.classList.toggle('active');
}
}))
function genreChoose(e){
let genres = {
Action: 28,
Adventure: 12,
Animation: 16,
Comedy: 35,
Crime: 80,
Documentary: 99,
Drama: 18,
Family: 10751,
Fantasy: 14,
History: 36,
Horror: 27,
Music: 10402,
Mystery: 9648,
Romance: 10749,
Science_Fiction: 878,
TV_Movie: 10770,
Thriller: 53,
War: 10752,
Western: 37
};
let url = new URL(lastUrl);
params = new URLSearchParams(url.search.slice(1));
if(e.currentTarget.classList.contains('active')){
removeValue(params, 'with_genres', genres[e.target.innerText]);
console.log('remove genre');
}else if(!e.target.classList.contains('active')){
url.searchParams.append('with_genres', genres[e.target.innerText]);
console.log('add genre');
}
fetchURL(url);
}
}
body {
margin: 0;
padding: 0;
--primary-color: #39445a;
--secondary-color: rgb(34, 24, 24);
background-color: var(--primary-color);
}
.app {
display: flex;
flex-direction: column;
width: 100%;
min-height: 100vh;
background-color: var(--secondary-color);
}
.app h1 {
font-family: "Montserrat", sans-serif;
text-transform: uppercase;
text-align: center;
color: #39445a;
font-size: 2em;
font-weight: 900;
}
.movie_container {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.movie {
cursor: pointer;
display: flex;
flex-direction: column;
width: 200px;
padding: 5px;
margin: 5px 15px;
background-color: #282c34;
border-radius: 10px;
font-family: "Montserrat", sans-serif;
}
.movie .movie_title {
font-family: "Montserrat", sans-serif;
text-transform: uppercase;
color: #fff;
font-size: 1em;
font-weight: 900;
text-align: center;
}
.genres {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin: 10px;
}
.genres .genre {
color: rgba(0, 0, 0, 0.87);
display: flex;
align-items: center;
text-transform: capitalize;
cursor: pointer;
background-color: #fff;
line-height: 3px;
border-radius: 10px;
padding: 5px 15px;
font-size: 0.850rem;
margin: 2px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.genres .genre.active {
color: #fff;
background-color: #3f51b5;
}
<div class="app">
<h1>discover movies</h1>
<div class="genres">
<div class="genre">action</div>
<div class="genre">adventure</div>
<div class="genre">animation</div>
<div class="genre">comedy</div>
<div class="genre">crime</div>
<div class="genre">documentary</div>
<div class="genre">drama</div>
<div class="genre">family</div>
<div class="genre">fantasy</div>
<div class="genre">history</div>
<div class="genre">horror</div>
<div class="genre">music</div>
<div class="genre">mystery</div>
<div class="genre">romance</div>
<div class="genre">science fiction</div>
<div class="genre">TV movie</div>
<div class="genre">thriller</div>
<div class="genre">war</div>
<div class="genre">western</div>
</div>
<div class="movie_container" id="movie_container">
<div class="movie">
<img src="img/dark_knight.jpg">
<span class="movie_title">the dark knight</span>
</div>
</div>
Upvotes: 2
Views: 4024
Reputation: 32002
Instead of recording the previous URL, getting the category picked and removing that one category from the list of categories in the parameter, you can simply construct the parameter again by selecting every element which has the genre
and active
class in the genreChoose
function, looping through each element and appending the category code to the URLSearchParams
.
To do this, we'll need to toggle the active
class before calling genreChoose
, so the category element would have the active
class when we select the element.
window.onload = function() {
const API_KEY = 'api_key=73b31f15b44a93f52789c751c34a5d7d';
const BASE_URL = 'https://api.themoviedb.org/3';
const API_URL = '/discover/movie?' + API_KEY + '&sort_by=popularity.desc';
const url = BASE_URL + API_URL;
const movie_container = document.getElementById('movie_container');
const prev = document.getElementById('prev');
const next = document.getElementById('next');
const ulTag = document.querySelectorAll('.numb');
const liTag = document.querySelector('li');
const genres = document.querySelectorAll('.genre');
fetchURL(url);
trendingMovies = movies => {
movie_container.innerHTML = '';
movies.forEach(movie => {
img = IMG_URL + movie.poster_path;
title = movie.title;
const movieEl = document.createElement('div');
movieEl.classList.add('movie');
movieEl.innerHTML =
`<img src="${img}">
<span class="movie_title">${title}</span>
`
movie_container.appendChild(movieEl);
});
}
function fetchURL(url) {
console.log(url);
fetch(url)
.then(res => res.json())
.then(data => {
trendingMovies(data.results);
})
}
const removeValue = (params, key, valueToRemove) => {
const values = params.getAll(key);
if (values.length) {
params.delete(key);
console.log('param deleted', params.toString());
for (const value of values) {
if (value !== valueToRemove) {
params.append(key, value);
console.log('params appended:', params.toString());
}
}
}
return params;
};
genres.forEach(el => el.addEventListener('click', (e) => {
if (e.target.classList.contains('active')) {
e.target.classList.remove('active');
} else if (!e.target.classList.contains('active')) {
e.target.classList.toggle('active');
}
genreChoose(e);
}))
function genreChoose(e) {
let genres = {
Action: 28,
Adventure: 12,
Animation: 16,
Comedy: 35,
Crime: 80,
Documentary: 99,
Drama: 18,
Family: 10751,
Fantasy: 14,
History: 36,
Horror: 27,
Music: 10402,
Mystery: 9648,
Romance: 10749,
Science_Fiction: 878,
TV_Movie: 10770,
Thriller: 53,
War: 10752,
Western: 37
};
params = new URLSearchParams('');
document.querySelectorAll('.genre.active').forEach(f => params.append('with_genres', genres[f.innerText]))
fetchURL(url + ([...params].length > 0 ? '&' : '') + params.toString());
}
}
body{margin:0;padding:0;--primary-color:#39445a;--secondary-color:rgb(34,24,24);background-color:var(--primary-color)}.app{display:flex;flex-direction:column;width:100%;min-height:100vh;background-color:var(--secondary-color)}.app h1{font-family:"Montserrat",sans-serif;text-transform:uppercase;text-align:center;color:#39445a;font-size:2em;font-weight:900}.movie_container{display:flex;flex-wrap:wrap;justify-content:center}.movie{cursor:pointer;display:flex;flex-direction:column;width:200px;padding:5px;margin:5px 15px;background-color:#282c34;border-radius:10px;font-family:"Montserrat",sans-serif}.movie .movie_title{font-family:"Montserrat",sans-serif;text-transform:uppercase;color:#fff;font-size:1em;font-weight:900;text-align:center}.genres{display:flex;justify-content:center;flex-wrap:wrap;margin:10px}.genres .genre{color:rgba(0,0,0,0.87);display:flex;align-items:center;text-transform:capitalize;cursor:pointer;background-color:#fff;line-height:3px;border-radius:10px;padding:5px 15px;font-size:0.850rem;margin:2px;font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif}.genres .genre.active{color:#fff;background-color:#3f51b5}
<div class=app><h1>discover movies</h1><div class=genres><div class=genre>action</div><div class=genre>adventure</div><div class=genre>animation</div><div class=genre>comedy</div><div class=genre>crime</div><div class=genre>documentary</div><div class=genre>drama</div><div class=genre>family</div><div class=genre>fantasy</div><div class=genre>history</div><div class=genre>horror</div><div class=genre>music</div><div class=genre>mystery</div><div class=genre>romance</div><div class=genre>science fiction</div><div class=genre>TV movie</div><div class=genre>thriller</div><div class=genre>war</div><div class=genre>western</div></div><div class=movie_container id=movie_container><div class=movie><img src=img/dark_knight.jpg> <span class=movie_title>the dark knight</span></div></div>
The HTML and CSS have been minified. They have not been altered.
The following 2 lines of code are all that are needed to implement this:
document.querySelectorAll('.genre.active').forEach(f => params.append('with_genres', genres[f.innerText]))
fetchURL(url + ([...params].length > 0 ? '&' : '') + params.toString());
document.querySelectorAll
will select every element which matches the CSS selector passed as a parameter, and return the selected elements as a NodeList. The selector in this case is '.genre.active'
, which selects all elements with the genre
and active
class (elements with the genre
class and not the active
class and vice/versa will not be selected).
forEach
accepts a callback function, which will be called for each item in the list, with the element passed as the parameter (in this case, f
). Inside the loop, we use URLSearchParams.append
to append the category code to the with_genres
parameter.
The ternary operator (([...params].length > 0 ? '&' : '')
) is used to add a &
between the URL and the GET parameters only if there are parameters. If there are no parameters, having a trailing &
is redundant.
Upvotes: 1
Reputation: 1075587
I don't think there's a one-method way to do it. You have to:
getAll
)Here's an example:
const removeValue = (params, key, valueToRemove) => {
const values = params.getAll(key);
if (values.length) {
params.delete(key);
for (const value of values) {
// BEWARE, remember the values will have been
// covnerted to string
if (value !== valueToRemove) {
params.append(key, value);
}
}
}
return params; // For chaining if desired
};
const params = new URLSearchParams();
params.append("test", "a");
params.append("test", "b");
params.append("test", "c");
console.log(params.toString());
removeValue(params, "test", "b");
console.log(params.toString());
Upvotes: 1