3

I'm making a movie sorter list, you enter the title and then the rating and it will show you the movies in order by rating. I have an array of objects and I managed to sort the array by rating, but I can't find a way to actually display the array in order on the HTML DOM.

I've tried for loops and forEach's but they don't work the way I want.

const movieTitle = document.querySelector(".movie-title");
const movieRating = document.querySelector(".movie-rating");
const movieList = document.querySelector(".movie-list");
const sortBtn = document.querySelector(".btn");

let movieStorage = [];

function sendMovie() {
    if(event.keyCode == 13) {
        if(movieTitle.value != "" && movieRating.value != "") {
            title = movieTitle.value;
            rating = parseInt(movieRating.value);

            movieStorage.push({
                title: title,
                rating: rating
            });

            // If rating of a is bigger than rating of b return 1, if not return -1
            movieStorage.sort((a, b) => (a.rating > b.rating) ? -1 : 1);
            console.log(movieStorage);

            addMovieToList(title, rating);

            movieTitle.value = "";
            movieRating.value = "";
        } else {
            console.log("Fields missing");
        }
    }
}

function addMovieToList(title, rating) {

    const div = document.createElement("div");
    div.className = "list-items";

    div.innerHTML = `
    <div class="item-title">
        <p>${title}</p>
    </div>

    <div class="item-rating">
        <p>${rating}</p>
    </div>

    <div class="item-delete">
        <i class="fa fa-trash trash-icon delete"></i>
    </div>
    `;

    movieList.appendChild(div);
}

function sortByRating(element) {
    for(let i = 0; i < movieStorage.length; i++) {
        element.innerHTML = `
        <div class="item-title">
            <p>${movieStorage[i].title}</p>
        </div>

        <div class="item-rating">
            <p>${movieStorage[i].rating}</p>
        </div>

        <div class="item-delete">
            <i class="fa fa-trash trash-icon delete"></i>
        </div>
        `;
    }
}

document.addEventListener("click", (e) => {
    const deleteIcon = e.target;

    const item = document.querySelector(".list-items");

    if(deleteIcon.classList.contains("delete")) {
        deleteIcon.parentElement.parentElement.remove(item);
    }
})
theblackips
  • 779
  • 4
  • 16

2 Answers2

2

tldr demo

After sorting the array, you need a way to reference movie divs to sort them. There are many ways to do it, what I chose is using id. When you create movie <div>, give it an ID unique for each movie name:

// Simple function to generate hash number for each string
function hashStr(stringValue) {
  var hash = 0, i, chr;
  if (stringValue.length === 0) return hash;
  for (i = 0; i < stringValue.length; i++) {
    chr   = stringValue.charCodeAt(i);
    hash  = ((hash << 5) - hash) + chr;
    hash |= 0; // Convert to 32bit integer
  }
  return hash;
}

const MOVIES = [
   {name: "a", rating: 3},
   {name: "b", rating: 6},
   {name: "c", rating: 3},
   {name: "d", rating: 2},
   {name: "e", rating: 1},
];

function showMovies() {
    const moviesDiv = document.querySelector("#movies");
    for(const movie of MOVIES) 
    {
        const id = "movie-"+hashStr(movie.name);
        // If there's no element with the ID, we need to create the DIV for the movie
        if(!document.querySelector("#"+id)) {
            const elm = document.createElement("div");
            elm.appendChild(new Text(movie.name + " ("+movie.rating+"/10)"));
            elm.id = id;
            elm.classList.add("movie");
            moviesDiv.appendChild(elm);
        }
    }
}

Then, when sorting, you can reference each movie by ID:

// Sort movies using given property (eg. "name")
// The second param determines sort direction
function sortBy(property, ascending=true) {
    MOVIES.sort((a,b) =>{
        return cmp(a[property], b[property], ascending);    
    });
    // Now after sorting the array, we can sort the HTML elements
    const moviesDiv = document.querySelector("#movies");
    let lastMovie = null;
    for(const movie of MOVIES) 
    {
        const id = "#movie-"+hashStr(movie.name);
        const movieDiv = document.querySelector(id);
        console.log(id, movieDiv);
        // If created
        if(movieDiv) {
            // remove and append after last processed movie (for the first movie, this will append to top)
            moviesDiv.insertBefore(movieDiv, lastMovie);
        }
    }
}
// Compare string and number, makes no sense for other types
function cmp(a,b, ascending=true) {
    if(typeof a=='number' && typeof b == "number") {
        return ascending ? a-b : b-a;
    }
    else if(typeof a=='string' && typeof b == "string"){
        return (ascending ? 1 : -1) * a.localeCompare(b);
    }
    else {
        return 0;
    }
}

When you add a movie, you just call sort again. You will need to remember the last sorting parameters for that.

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • I get this code, though I think it's a little bit too advanced for me, but still, thank you very much :) – Juan Martinez Aug 24 '19 at 12:13
  • If it works for you, remember you can mark the answer as accepted. If you do not understand some part of the code, let me know and I will explain. The whole trick that this answer is based on is having each DIV have an ID that is linked to it's movie. – Tomáš Zato Aug 24 '19 at 12:25
  • The hashStr function is the only thing I can't wrap my head around. – Juan Martinez Aug 24 '19 at 13:07
  • 1
    @JuanMartinez You really don't need to, it's pretty much one of many ways you could create a number for a string. I took it from [this question](https://stackoverflow.com/q/7616461/607407). You can use any other if you like. I picked the short and simple one. – Tomáš Zato Aug 24 '19 at 13:12
  • How do I make the variable _lastMovie_ know which one was the last movie? The sorting function isn't working, I think because of that. – Juan Martinez Aug 24 '19 at 14:13
  • Never mind, your code works. I had to make a few changes on the elements that were targeted and somehow it works now! Thank you! – Juan Martinez Aug 24 '19 at 16:06
1

Your sort will work fine. The problem is that after you've sorted you can't just display that movie, you have to redisplay the entire list. You're almost there with your sortByRating method, but it doesn't recreate the entire list correctly. Try something like:

function showMoviesList(element) {
    let innerHTML = "";
    for (let i = 0; i < movieStorage.length; i++) {
        innerHTML += `
        <div class="item-title">
            <p>${movieStorage[i].title}</p>
        </div>

        <div class="item-rating">
            <p>${movieStorage[i].rating}</p>
        </div>

        <div class="item-delete">
            <i class="fa fa-trash trash-icon delete"></i>
        </div>
        `;
    }
    element.innerHTML = innerHTML;
}

This resets the inner HTML of the element to the complete movie list in order every time it's called.

Now call showMoviesList(movieList) instead of calling addMovieToList in sendMovie.

Rich N
  • 8,939
  • 3
  • 26
  • 33
  • This works! I just had to call showMoviesList(listItem) instead and it worked perfectly, thank you! – Juan Martinez Aug 24 '19 at 13:01
  • Note that resetting inner HTML is computationally expensive (the browser needs to parse the DOM. You can increase performance by changing inner HTML for element that is not part of DOM or use a DocumentFragment. But in my opinion, it's best to not re-create HTML. – Tomáš Zato Aug 24 '19 at 13:14