No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Filtrando películas por categoría

11/17
Recursos

Aportes 41

Preguntas 7

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

o inicia sesión.

The best comment " Esa película es horrible" jajaja

¿Qué opinan de esta solución?
.

No sé si más adelante lo corrige el profe, pero al momento de entrar en una categoría que contiene un espacio en su título nos encontramos esto:

Pero pues lo podemos solucionar con un simple replace:p

Para que la página inicie arriba y no en el footer, agregar en la función categoriesPage() el siguiente código:

window.scroll(0,0);

Para solucionar el segundo reto (ahorrarnos líneas de código para una funcionalidad que se repite), lo que hice fue hacer una función llamada printMoviePosters, que recibe como paråmetros el objeto movies, y la sección en la que corresponde. Luego, cada función que la necesite la llamará, pasándole los parámetros correspondientes:

const printMoviePosters = (movies, section) => {
    section.innerHTML = "";

    movies.forEach(movie => {
        const movieContainer = document.createElement('div');
        movieContainer.classList.add('movie-container');

        const movieImg = document.createElement('img');
        movieImg.classList.add('movie-img');
        movieImg.setAttribute('alt', movie.title);
        movieImg.setAttribute(
            'src',
            `${POSTER_300W_URL}${movie.poster_path}`
        );

        movieContainer.appendChild(movieImg);
        section.appendChild(movieContainer);

    });
}
 
const getTrendingMoviesPreview = async () => {
    const { data } = await api('/trending/movie/day');
    const movies = data.results;

    printMoviePosters(movies, trendingMoviesPreviewList);
}

const getMoviesByCategory = async (categoryId) => {
    const { data } = await api('/discover/movie', {
        params: {
            with_genres: categoryId,
        },
    }
    );
    const movies = data.results;

    printMoviePosters(movies, genericSection);
}

En cuanto al reto del problema del scroll, creo haberlo solucionado agregando el scrollTo(0,0) en la función que se dispara al hacer click en el botón categoryTitle:

        categoryTitle.addEventListener('click', () => {
            location.hash = `#category=${category.id}-${category.name}`;
            window.scrollTo(0, 0);
        })

Se me ocurrió como solución alternativa para mostrar la categoría al usuario, colocarla como parte del titulo

document.title=`PlatziMovies ${category.name}`;

Que les parece?

Investigue el scrollTo(cord-x, cord-y);

con este método solucionamos el problema del scroll que aparece hasta abajo, solo debemos insertar el código en la función de navegación de categoriesPage:

window.scrollTo(0,0);

de este modo se ubicara al inicio de la pagina.

Hay otro detalle que cuando hacemos un refresh (f5), o usamos el botón de ir a atras en el navegador, si tenemos el scroll hasta abajo este no cambia su posición.

Un comportamiento optimo seria que si actualizamos la pagina o le damos hacia atras o adelante, este se ubique al inicio de la pagina, para solucionar esto también podemos usar el scrollTo anclado a un evento.

window.onbeforeunload = () => {
    scrollTo(0,0);
}

Este bloque de código, va en el main.js… ahora cada vez que actualicemos o le demos hacia atrás, la vista de la pagina se ubicara desde el inicio.

Para evitar que Ponga en el Title los caracteres codificados % de espacio y acentuación se puede usar el:

decodeURIComponent()

en el innerHTML
es decir: en el navigation.js

const [categ, categoryData] = location.hash.split('=');// ['#category','id-name']
    const [categoryId, categoryName] = categoryData.split('-');

    headerCategoryTitle.innerHTML= decodeURIComponent(categoryName);
    getMoviesByCategory(categoryId);

Y para los que han usado la funcion insertAdjacentHTML en el main.
esta es una propuesta que funciona para enviar el hash del evento click:

categories.forEach(category => {
        const categoryId = category.id;
        const categoryName = category.name;
        //const encodedCategoryName = encodeURIComponent(categoryName);
        const hash = `#category=${categoryId}-${categoryName}`;
  
        categoriesPreviewList.insertAdjacentHTML('beforeend', `
          <div class="category-container">
            <h3 id="id${categoryId}" class="category-title">${categoryName}</h3>
          </div>
        `);
  
        const categoryTitle = document.querySelector(`#id${categoryId}`);
        categoryTitle.addEventListener('click', () => {
          location.hash = hash;
        });
      });

Like si antes de llegar a esta clase ya habías desarrollado esta funcionalidad jajajaja.

Para evitarnos escribir el mismo código que renderice cada película (ya sea en el home o en la lista de películas por categoría), podríamos crear una función al cual se le pase dos parámetros, la data y el nodo donde se deben ‘pintar’.
.
Puede ser algo más o menos así:
.

function renderMoviesHtml(data, node){
  data.forEach(movie => {
    const movieContainer = document.createElement('div');
    movieContainer.classList.add('movie-container');
    const movieImg       = document.createElement('img');
    movieImg.classList.add('movie-img');
    movieImg.setAttribute('alt', movie.title);
    movieImg.setAttribute('src', `https://image.tmdb.org/t/p/w300/${movie.poster_path}`);
    movieContainer.appendChild(movieImg);
    node.appendChild(movieContainer)
  });
}

.
Luego sería llamar esta función pasándole los respectivos argumentos y ahorramos varias líneas de código.

Una clse genial!!
Creo que, para los que van comenzando pueden volver a repasae éste curso (proyecto) para un curso como “webpack”. Además, como el de “Single Page Application”.

Esta era mi solución:

  const index = window.location.hash.indexOf('=')
  const index2 = window.location.hash.indexOf('-')
  const query = window.location.hash.substring(index + 1, index2) 

es muy forzada

Solución al reto de cargar la página desde el top: basta con poner esta línea de código en nuestra función:

  window.scrollTo(0, 0);

Ejemplo:

Y para el que quiera profundizar más, esta es su documentación:
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop

Saludos! 😄

Buenas!
Puede que a alguien (que no este usando Axios) le suceda lo mismo que yo…
A la hora de hacer de ejecutar el codigo nos sale un Error 401 por consola en la funcion getMoviesByCategory() mas precisamente en el fetch()…
Esto se debe a que estamos pasando los Query Parameters o los EndPoints mal…
Esta es la URL a la que debemos hacer la peticion:
https://api.themoviedb.org/3/discover/movie?api_key=${API_KEY}&with_genres=${categoryID}
y es importante pasar primero la “API KEY” sino de otra forma no va a autenticarar la petición…

<code> 

async function getCategoriesPreview() {
  // solicitud a trending de peliculas  para el home
  // const res = await fetch(
  //   "https://api.themoviedb.org/3/genre/movie/list?api_key=" + API_KEY
  // );
  const { data } = await api("genre/movie/list");
  const categories = data.genres;
  // console.log({ categories, data });
  categoriesPreviewList.innerHTML = "";

  categories.forEach((category) => {
    const categoriesPreviewList = document.querySelector(
      "#categoriesPreview .categoriesPreview-list"
    ); // lo que hago aca es tomar el elemento que esta dentro del id trendingpreview y que contiene trendingpreview movielist

    const categoryContainer = document.createElement("div");
    categoryContainer.classList.add("category-container");

    const categoryTitle = document.createElement("h3"); // estoy creando un nuevo elemento dentro de hatml (IMG)
    categoryTitle.classList.add("category-title"); // estoy añadiendole una clase a la imagen
    categoryTitle.setAttribute("id", "id" + category.id); // estoy añadiendo el atributo alt y su nombre invocando movie.title ( titulo que me envia la api)
    categoryTitle.addEventListener("click", () => {
      location.hash = `#category=${category.id}-${category.name}`;
    });
    const categoryTitleText = document.createTextNode(category.name);
    categoryContainer.scrollTop; // asigno scrollTop al elemento que me direccionaré

    categoryTitle.appendChild(categoryTitleText);
    categoryContainer.appendChild(categoryTitle); // coloco dentro del movie container el movieImg
    categoriesPreviewList.appendChild(categoryContainer);
  });

Esta fue mi solución para re utilizar el código de las películas:

async function getTrendingMoviesPreview() {
    body.scrollTop = 0
    const { data } = await api(URL_TrendingMovies)
    const movies = data.results

    trendingMoviesPreviewList.innerHTML = ''
    renderMovies(trendingMoviesPreviewList, movies)
}

async function getMoviesByCategory(id) {
    const { data } = await api(URL_DiscoverMovie,{
        params: {
            with_genres: id
        }
    })
    const movies = data.results

    genericSection.innerHTML = ''
    renderMovies(genericSection, movies)
}

function renderMovies(mainSection, movies) {
    movies.forEach(movie => {
        const movieContainer = document.createElement('div')
        movieContainer.classList.add('movie-container')

        const movieImg = document.createElement('img')
        movieImg.classList.add('movie-img')
        movieImg.setAttribute('alt', movie.title)
        movieImg.setAttribute('src', `https://image.tmdb.org/t/p/w300${movie.poster_path}`)

        movieContainer.appendChild(movieImg)
        mainSection.appendChild(movieContainer)
    })
} 

También corregí el detalle de los espacios en el títulos de las categorías:

function categoriesPage() {
    // console.log('Category!!')
    headerSection.classList.remove('header-container--long')
    headerSection.style.background = ''
    arrowBtn.classList.remove('inactive')
    arrowBtn.classList.remove('header-arrow--white')
    headerTitle.classList.add('inactive')
    headerCategoryTitle.classList.remove('inactive')
    searchForm.classList.add('inactive')
    
    trendingPreviewSection.classList.add('inactive')
    categoriesPreviewSection.classList.add('inactive')
    genericSection.classList.remove('inactive')
    movieDetailSection.classList.add('inactive')
    
    const [_ , categoryData] = location.hash.split('=')
    const [categoryId, categoryName] = categoryData.split('-')

    headerCategoryTitle.innerText = categoryName.replace('_', ' ')
    getMoviesByCategory(categoryId)
}

![](

No logré lo del scroll 😫😭

Mi solución a uno de los retos

Me quise adelantar a la clase y hice el filtro por las peliculas en tendencia jajaja F

const filterCategoriesMovies =  async (category) => {


    const {data} = await API.get('trending/movie/day')
    const movies = data.results

    const filterMovies = movies.filter(movie => movie.genre_ids.includes(category))

    genericSection.innerHTML = ""

    filterMovies.forEach(movie => {

        const movieContainer = document.createElement('div')
        movieContainer.classList.add('movie-container')

        const img = document.createElement('img')
        img.src = `https://image.tmdb.org/t/p/w300/${movie.poster_path}`
        img.title = movie.original_title
        img.alt = movie.original_title
        img.classList.add('movie-img')

        movieContainer.appendChild(img)
        genericSection.appendChild(movieContainer)
    })

    headerCategoryTitle.style.after = filterMovies.length

}

const getCategories = async () => {

    const {data} = await API.get('genre/movie/list')
    const categories = data.genres
     categoriesPreviewList.innerHTML = ""
     categories.forEach(category => {
      const categoryName = document.createElement('h3')
        categoryName.classList.add('category-title')
        categoryName.textContent = category.name 
        categoryName.id = `id${category.id}`
        categoryName.onclick =  () => {

            location.hash = `category=${category.id}-${category.name}`
            headerCategoryTitle.textContent = `Results for ${category.name.toLowerCase()} movies` 
            filterCategoriesMovies(category.id)
        }

        categoryContainer.appendChild(categoryName)
        categoriesPreviewList.appendChild(categoryContainer)
    })
}

Si su proyecto esta en español lo más probable es que cuando incluyan las categorías en su URI esta se codifique en Unicode para evitar caracteres y signos especiales como las tildes. Pueden utilizar los métodos …

encode/decodeURIComponent()

para no perder dichos caracteres.
Ejemplo:

async function getCategoriesPreview() {
    const { data } = await api('genre/movie/list');
    const categories = data.genres;
    categoriesPreviewList.innerHTML = "";
    categories.forEach(category => {
        const categoryContainer = document.createElement('div');
        categoryContainer.classList.add('category-container');

        const categoryTitle = document.createElement('h3');
        categoryTitle.classList.add('category-title');
        categoryTitle.setAttribute('id', `id${category.id}`);
        categoryTitle.addEventListener('click', ()=>{
            location.hash = encodeURIComponent(`category=${category.id}-${category.name}`);
        })
        const categoryTitleText = document.createTextNode(category.name);

        categoryTitle.appendChild(categoryTitleText);
        categoryContainer.appendChild(categoryTitle);
        categoriesPreviewList.appendChild(categoryContainer);
    });
}
function categoriesPage() {
    console.log('categories!!');
  
    headerSection.classList.remove('header-container--long');
    headerSection.style.background = '';
    arrowBtn.classList.remove('inactive');
    arrowBtn.classList.remove('header-arrow--white');
    headerTitle.classList.add('inactive');
    headerCategoryTitle.classList.remove('inactive');
    searchForm.classList.add('inactive');
  
    trendingPreviewSection.classList.add('inactive');
    categoriesPreviewSection.classList.add('inactive');
    genericSection.classList.remove('inactive');
    movieDetailSection.classList.add('inactive');

    const [_,categoryData] = decodeURIComponent(location.hash).split('=');
    const [categoryId, categoryName]=categoryData.split('-');
    console.log(categoryName)
    headerCategoryTitle.innerHTML=categoryName;
    getMoviesByCategory(categoryId)
  }

const [__,idOk]=categoryId.split(’%20’);
getMoviesByCategory(idOk,categoryName)
Para no seleccionar el %20 de la url

Para conseguir el aporte esta fue mi solución:

Mi solucion:
Hice una funcion con el codigo html y sus respectivos argumentos para añadirle

function appendHTML(data, container, containerClass, optionalClass = ""){
    data.forEach(element=>{
        container.innerHTML += 
        `
        <div class=${containerClass}>
        <img src=${URL_IMG_POSTER + element.poster_path} alt=${element.title} class="img-film" width=100%>
        <h3 class="movie-title ${optionalClass}">${element.title}</h3>
        <p class="movieDetail-score">${element.vote_average}</p></div>
        `
    })
    
}

Para poner en español las respuestas de la api (Categorías y algunos posters de Series y Películas) usando Axios, definimos en los parametros de la solicitud el parametro ‘language’ : ‘es’.
.

const api = axios.create({
    baseURL: 'https://api.themoviedb.org/3',
    headers: {
        'Content-Type': 'application/json;charset=utf-8'
    },
    params:{
        'api_key': API_KEY,
        'language': 'es'  //para que las categorias lleguen en español
    }
})

Si tienen el problema de que en la URL aparece “%20” pueden usar la siguiente expresión regular para transformar espacios en guiones

str = str.replace(/ /g, '-')`

Dejo esto por acá porque seguro que a alguien le va a servir… “Genre” es una de esas palabras en inglés que tiene una pronunciación muy rara… viene siendo algo como: “shonra”.

En google translate pueden darle al iconito de sonido para escuchar como se dice:

https://translate.google.com/?hl=es&sl=en&tl=es&text=genre&op=translate

Antes de ver la solucion de Juan hice mi propia solucion para conseguir el id del string hice esta linea de codigo

const catId = hash.split('')
			.filter((n) => {
   				 return Number(n)
			})
			.join('')

Importantisimo en pensar en el mantenimiento futuro, a mayor abstración de código, se hace más fácil el mantenimiento.

Este proyecto se está poniendo interesante. Yo nunca encuentro una película para ver. Ahora voy a poder buscar en este proyecto 😄

solución para obtener el id del location.hash.

let arrayHash = location.hash.split(’-’)[0];
let id = arrayStringHash.split(’=’)[1];
console.log(id)

Otra manera de obtener el ID del hash es de la siguiente manera:
.

const [hash, nameCategory] = location.hash.replace('#category=', '').split('-');

Supongo que justo ese método de “colocar el id seguido de un - para luego colocar el nombre de la sección” es lo que utilizan aquí en platzi, je je. De ahí que las url de los cursos y las clases sean cosas como “/platzi.com/clases/2986-api-practico/48456-filtrando-peliculas-por-categoria/”.

Esta es mi solución usando templates en HTML, me parece que se optimiza más el código.

const getMoviesByCategory = async (id) =>{
  try {
    let options={
      params: {
        with_genres: id,
      }
    }
    const {data} = await api('discover/movie', options),
      movies = data.results;
    console.log(movies);
  
  
    movies.forEach(el => {
      $template_categoriesPreview.querySelector('.movie_img').src = `https://image.tmdb.org/t/p/w300${el.poster_path}`;
      
      let $clone = d.importNode($template_categoriesPreview,  true); 
      $fragment.appendChild($clone);
    });
    $genericSection.innerHTML ="",
    $genericSection.appendChild($fragment);

  } catch (error) {
    console.log(error);
  }
  }

Para el reto les dejo la función que usé, por cursos anteriores y documentación que he leído, decían que no se recomienda usar innerHTML para manejo del DOM por cuestiones de seguridad y también que es menos eficiente, en mi caso hago uso de la función replaceChildren() que en caso de no pasarle ningún parámetro elimina los hijos de un contenedor

function createMovies(movies, container){
    let moviesList = [];
    container.replaceChildren();
    
    movies.forEach( movie  => {
        const movieContainer = document.createElement('div');
        const movieImg = document.createElement('img');
        movieContainer.classList.add('movie-container');
        movieImg.classList.add('movie-img');
        movieImg.setAttribute('alt', movie.title);
        movieImg.setAttribute('src', 'https://image.tmdb.org/t/p/w300/'+movie.poster_path);
        
        movieContainer.append(movieImg);
        moviesList.push(movieContainer);
    });
    container.append(...moviesList);
}

Esta es la solucion de uno de los retos, para que la pagina se cargue de primeras.
Solo se coloca esta linea en la función de categorías en el navigation.js

window.scrollTo(0, 0);

Y para el reto de encapsular código repetido, supongo que se hace una función mas que nada solo para la petición a la api no?, ya que en mi caso las secciones tienen una disposición diferente por ello la manipulacion del DOM de c/u es totalmente diferente

La solución del profe en la unión del id con el nombre de la categoría se conoce como un slug de dos variables, id and name.
.
La solución que yo agregado para tener los id y el name de la categoría en cada nodo es agregando un dataset la adición del atributo ‘data-namedataset’, esto es para hacer que el contenedor agreguemos un solo evento y aplciar la delegación de eventos detectando el target que sería en este caso el h3.
.
Es recomendable hacerlo así para optimizar carga de eventos y no hacerlo en cada loop.

getNode.categoriesPreviewList.addEventListener('click', (e) => {
    const target = e.target;
    if (target && target.nodeName === 'H3') {
        const categoryContainerNode = target.parentNode;
        const categoryID = categoryContainerNode.dataset.categoryid;
        const categoryName = categoryContainerNode.dataset.categoryname.toLowerCase();
        window.location.hash = `category=${categoryID}-${categoryName}`;
    }
});

.
Mi Repositorio

Otra opción para sacar el ID del hash es usando Regex   ```js let id = location.hash.match(/[\d]{1,10}/g);    ```De la condena de texto del hash saco cualquier cadena numérica de una extensión de 1 a 10 caracteres de longitud
```js let data = 'category=10751-family' parseInt(data.split('=')[1].split('-')[0]) ```
No estoy usando axios. Como hago fetch en este caso?

En los cursos al inicio de la escuela, los profesores decian: Si se repite, debe ser una función:

Para usar:

  • window.scrollTo : Desplaza el visor a un conjunto de coordenadas en el documento.
    window.scrollTo(0, 0) : Para mover siempre al inicio.

  • decoreURI() : Sustituye a cada secuencia de escape codificado en URI con el carácter que representa.
    headerCategoryTitle.innerHTML = decodeURI(categoryName);

La solución que propongo para el reto del código duplicado es:
.

  1. Crear una función pura (que no afecte directamente a algún elemento o variable fuera de ella) que reciba sólo el array de películas de la API. Ésta retornará un nuevo array con todos los nodos de las películas.

.

.

  1. Usar append en lugar de appendChild, ya que así se pueden mandar múltiples nodos en vez de sólo uno; y se es más óptimo, ya que no se hacen tantos appends sino uno solo.

.

  1. El spread operator hará que se expanda el array de nodos en la función append aplicada al contenedor que corresponda, ya que esa responsabilidad sigue perteneciendo a la que hace el llamado a la API.

.