Search code examples
javascripthtmlweb-componentcustom-element

nested custom element not showing the data


im trying to make function that show all the movie from the themoviedb api with nested custom element but it doesn't appear on the web

here the code for the custom element movie-container.js : `

import './movie-item.js';
class movieList extends HTMLElement{
    constructor(){
        super();
        this.shadowDOM = this.attachShadow({mode : 'open'});
    }
    set movies(movies){
        this._movies = movies;
        this.render;
    }

    render(){
        this.shadowDOM.innerHTML = '';
        this._movies.forEach(item => {
            const movieItem = document.createElement("movie-item");
            movieItem.item = item;
            this.shadowDOM.appendChild(movieItem);
        });
    }
}

customElements.define('movie-container',movieList);

`

movie-item.js : `

class MovieItem extends HTMLElement {
    constructor(){
        super();
        this.shadowDOM = this.attachShadow({mode : 'open'});
    }
    set movie(movie) {
        this._movie = movie;
        this.render();
    }
    render(){
        this.shadowDOM.innerHTML = `
        <style>
            * {
                margin : 0;
                padding : 0;
            }
                :host {
                display: block;
                margin-bottom: 18px;
                box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
                border-radius: 10px;
                overflow: hidden;
            }
            .movie-info {
                color: white;
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 0.5rem 1rem 1rem;
                letter-spacing: 0.5px;
            }
        
            .movie-info h3 {
                margin-top: 0;
            }
            
            .movie-info span {
                background-color: #145455;
                padding: 0.25rem 0.5rem;
                border-radius: 3px;
                font-weight: bold;
            }
        
            .movie-info span.green {
                    color: lightgreen;
            }
            .movie-info span.orange {
                    color: orange;
            }
            .movie-info span.red {
                    color: red;
            }
            
            .description {
                    position: absolute;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    background-color: white;
                    padding: 1rem;
                    max-height: 100%;
                    transform: translateY(101%);
                    transition: .2s ease;
            }
        </style>

            <img src="${'https://image.tmdb.org/t/p/w500'+this._data.poster_path}" alt="${this._data.title}">

            <div class="movie-info">
                <h3>${this._data.title}</h3>
                <span class="${getColor(this._data.vote_average)}">${this._data.vote_average}</span>
            </div>

            <div class="description">
                ${this._data.overview}
            </div>
    `
        ;
    }
}

customElements.define('movie-item',MovieItem);

`

the render function : `

const movieContainerElement = document.querySelector('movie-container');
    const renderAllMovie = (results) => {
        movieContainerElement.movies = results;
        
    }

`

class MovieList extends HTMLElement{
    constructor(){
        super();
        this.attachShadow({mode : 'open'});
    }
    set movies(movies){
        this._movies = movies;
        this.render();
    }

    render(){
        this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: flex;
                    flex-wrap: wrap;
                    justify-content: center;
                }
            </style>
        `;
        this._movies.forEach(item => {
            const movieItem = document.createElement("movie-item");
            movieItem.movie = item;
            this.shadowRoot.appendChild(movieItem);
        });
    }
}

customElements.define('movie-container',MovieList);

class MovieItem extends HTMLElement {
    constructor(){
        super();
        this.attachShadow({mode : 'open'});
    }
    set movie(item) {
        this._item = item;
        this.render();
    }
    render(){
        this.shadowRoot.innerHTML = `
        <style>
            :host {
                width: 300px;
                margin: 1rem;
                border-radius: 3px;
                box-shadow: 0.2px 4px 5px rgba(0, 0, 0, 0.1);
                position: relative;
                overflow: hidden;
                background-color: #0F3D3E;
            }

            :host img {
                    width: 100%;
            }
            
            :host:hover .description{
                    transform: translateY(0);
            }
            
            .movie-info {
                color: white;
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 0.5rem 1rem 1rem;
                letter-spacing: 0.5px;
            }
        
            .movie-info h3 {
                margin-top: 0;
            }
            
            .movie-info span {
                background-color: #145455;
                padding: 0.25rem 0.5rem;
                border-radius: 3px;
                font-weight: bold;
            }
        
            .movie-info span.green {
                    color: lightgreen;
            }
            .movie-info span.orange {
                    color: orange;
            }
            .movie-info span.red {
                    color: red;
            }
            
            .description {
                    position: absolute;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    background-color: white;
                    padding: 1rem;
                    max-height: 100%;
                    transform: translateY(101%);
                    transition: .2s ease;
            }
        </style>

            <img src="${'https://image.tmdb.org/t/p/w500'+this._item.poster_path}" alt="${this._item.title}">

            <div class="movie-info">
                <h3>${this._item.title}</h3>
                <span class="${getColor(this._item.vote_average)}">${this._item.vote_average}</span>
            </div>

            <div class="description">
                ${this._item.overview}
            </div>
    `
        ;
    }
}

customElements.define('movie-item',MovieItem);



//the data from API
const data = [
  {
    "adult": false,
    "backdrop_path": "/y5Z0WesTjvn59jP6yo459eUsbli.jpg",
    "genre_ids": [
      27,
      53
    ],
    "id": 663712,
    "original_language": "en",
    "original_title": "Terrifier 2",
    "overview": "After being resurrected by a sinister entity, Art the Clown returns to Miles County where he must hunt down and destroy a teenage girl and her younger brother on Halloween night.  As the body count rises, the siblings fight to stay alive while uncovering the true nature of Art's evil intent.",
    "popularity": 5162.285,
    "poster_path": "/yw8NQyvbeNXoZO6v4SEXrgQ27Ll.jpg",
    "release_date": "2022-10-06",
    "title": "Terrifier 2",
    "video": false,
    "vote_average": 7.4,
    "vote_count": 176
  },
  {
    "adult": false,
    "backdrop_path": "/bQXAqRx2Fgc46uCVWgoPz5L5Dtr.jpg",
    "genre_ids": [
      28,
      878,
      14
    ],
    "id": 436270,
    "original_language": "en",
    "original_title": "Black Adam",
    "overview": "Nearly 5,000 years after he was bestowed with the almighty powers of the Egyptian gods—and imprisoned just as quickly—Black Adam is freed from his earthly tomb, ready to unleash his unique form of justice on the modern world.",
    "popularity": 3835.264,
    "poster_path": "/3zXceNTtyj5FLjwQXuPvLYK5YYL.jpg",
    "release_date": "2022-10-19",
    "title": "Black Adam",
    "video": false,
    "vote_average": 7.1,
    "vote_count": 580
  },
  {
    "adult": false,
    "backdrop_path": "/tIX6j3NzadlwGcJ52nuWdmtOQkg.jpg",
    "genre_ids": [
      27,
      53,
      9648
    ],
    "id": 717728,
    "original_language": "en",
    "original_title": "Jeepers Creepers: Reborn",
    "overview": "Forced to travel with her boyfriend to a horror festival, Laine begins to experience disturbing visions associated with the urban legend of The Creeper. As the festival arrives and the blood-soaked entertainment builds to a frenzy, she becomes the center of it while something unearthly has been summoned.",
    "popularity": 2504.094,
    "poster_path": "/aGBuiirBIQ7o64FmJxO53eYDuro.jpg",
    "release_date": "2022-09-15",
    "title": "Jeepers Creepers: Reborn",
    "video": false,
    "vote_average": 5.8,
    "vote_count": 378
  },
]
const getColor = (vote => {
        if(vote >= 8){
            return 'green'
        }else if(vote >= 5){
            return 'orange'
        }else {
            return 'red'
        }
    });
//the render function
 const movieContainerElement = document.querySelector('movie-container');
 const renderAllMovie = (results) => {
        movieContainerElement.movies = results;
    }
    
renderAllMovie(data);
@import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap');
* {
     margin: 0;
     padding: 0;
     box-sizing: border-box;
}
a {
     text-decoration: none;
     color: black;
}
li {
     list-style: none;
}
body {
     font-size: 100%;
     background-color: #E2DCC8;
}
body {
     font-family: 'Montserrat', sans-serif;
}

search-bar {
     float: right;
     margin-top: 10px;
}

nav {
     overflow: auto;
}

.wrap {
     background-color: #0F3D3E;
     overflow: auto;
     padding: 0 30px;
}

.wrap a {
     float: left;
     display: block;
     color: #E2DCC8;
     text-align: center;
     padding: 15px;
     font-size: 17px;
     transition: .5s ease;
     background-color: #0F3D3E;
}


@media screen and (max-width: 600px) {
     .wrap {
          padding: 0;
     }
     .wrap nav search-bar{
          float: none;
     }
     .wrap nav a {
          float: none;
          display: block;
          text-align: left;
          width: 100%;
          margin: 0;
          padding: 14;
     }
}

.no-results {
     margin-top: 50px;
     color: #0F3D3E;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%=htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <header>
        <div class="wrap">
            <nav id="nav" class="desktopNav">
                <div class="left-col">
                    <a href="#"><h1><i class="logo fa-sharp fa-solid fa-film"></i> Movie List</h1></a>
                </div>
            </nav>
        </div>
    </header>
    <main>
        <movie-container></movie-container>
    </main>
</body>
</html>

when im not using custom element, it works, the custom element appears when i inspect the web but the movie item doesn't appear, it just print out #shadow-root (open)


Solution

  • It seems you forgot to call render inside your movies setter in your class movieList (btw, class names per convention should start with a capital letter).

    Replace

    set movies(movies){
        this._movies = movies;
        this.render; // <-- not called
    }
    

    with

    set movies(movies){
        this._movies = movies;
        this.render(); // call it!
    }
    

    You cannot create complex selectors with the :host pseudo class; you need to use the :host(selector) function.

    Replace

    :host:hover .description {
      transform: translateY(0);
    }
    

    with

    :host(:hover) .description {
      transform: translateY(0);
    }