Carl
Carl

Reputation: 357

How can I delete just one parameter with urlsearchparams.delete()?

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

Answers (2)

Spectric
Spectric

Reputation: 32002

Your current approach is much too complicated

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.

Explanation

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

T.J. Crowder
T.J. Crowder

Reputation: 1075587

I don't think there's a one-method way to do it. You have to:

  • Get all the current values (getAll)
  • Remove the key entirely
  • Add back the values you want to keep

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

Related Questions