diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..611e577e Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 49093dd2..d4e22426 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,19 @@ # Project Cinema -We want to create a movie search engine. To power it we will use the [Open Movie Database](http://www.omdbapi.com) API. +This is a movie search engine and data is fetched from [Open Movie Database](http://www.omdbapi.com) API. -To start using the OMDB API you will first need to sign up with them to receive and API key. The key issued to you will allow you 1000 requests per day and you will need to include this key as part of every request. +There are two sets of html and js files; each is responsible for one page. -To get started, fork and clone this repo. Please submit a pull request after your first commit and push commits regularly. +The movie.html and movie.js are to create the movie search functionality. Within this page, the users can search for movies using key words, read expanded movie details, use pagination to switch between pages, and save movies to favorites. A preview window will appear once three or more letters are typed in the search box. -You should complete as many of the following tasks as you can. +The favorite.html and favorite.js are to save your favorite movies using local storage. The up and down buttons can be used to re-order them. -- [ ] Work using mobile first, that is create the mobile version first and add tablet and desktop versions after. -- [ ] Create an HTML page which should have a `form` at the top which contains a text input and a submit button. Below it should have a placeholder element for the returned results. -- [ ] Use JavaScript to capture the `submit` event in your search form, extract the query string from the text input and use that to make an API call to the Open Movie Database API to search for films which match the query string using `fetch`. `console.log` the results -- [ ] Display the data returned by the API including title, year and poster picture +To switch between search and favorites, you can use the go to buttons at the top of the pages. -**Movie details** +Both pages are responsive to different screen widths using the same css style file. -- [ ] Adjust your layout to create room for a detailed view of movie information -- [ ] Capture clicks on your movie results items and use that information to make another request to the API for detailed movie information. Using event delegation will help you here. `console.log` the returned result -- [ ] Display the detailed movie result in the in the details view you created earlier -- [ ] Make your design responsive and ensure it looks great at different screen widths -**Your own feature** -- [ ] Implement any feature you would find useful or interesting - -**Stretch goals** - -- [ ] Implement pagination so that users can navigate between all movies in search results rather than just the first ten -- [ ] Create a favourites list. It's up to you how you would add items to favourites. You could add a button or otherwise. Display a list of favourites somewhere on your page. -- [ ] Make the favourites list sortable. Add `up` and `down` buttons to your favourites which on click will move the result in relevant direction -- [ ] Save favourites locally using `localStorage` so that favourites persist in browser after refresh -- [ ] Let's create a search preview. It should listen for change events on input events and submit a search request with current query string. Display the search preview results in an absolute positioned container just below the search box. -Hint: You may want to kick of the searching after at least 3 characters have been typed. ## Objectives @@ -42,7 +24,3 @@ Hint: You may want to kick of the searching after at least 3 characters have bee * Your code should not throw errors and handle edge cases gracefully. For example not break if server fails to return expected results * Use BEM methodology to style your page * Try to use pure functions as much as possible, but keep in mind it will not be possible to make all functions pure. - -## README.md - -When finished, include a README.md in your repo. Someone who is not familiar with the project should be able to look at it and understand what it is and what to do with it. Explain functionality created, mention any outstanding issues and possible features you would include if you had more time. List technologies used to create the app. Include a screenshot of your app in the README. diff --git a/button.png b/button.png new file mode 100644 index 00000000..486970be Binary files /dev/null and b/button.png differ diff --git a/favorite.html b/favorite.html new file mode 100644 index 00000000..21c0de00 --- /dev/null +++ b/favorite.html @@ -0,0 +1,22 @@ + + + + + + + + + + +

My Favorites

+ + +
    +
+ + + + diff --git a/favorite.jpg b/favorite.jpg new file mode 100644 index 00000000..93e64c52 Binary files /dev/null and b/favorite.jpg differ diff --git a/favorite.js b/favorite.js new file mode 100644 index 00000000..e911c14c --- /dev/null +++ b/favorite.js @@ -0,0 +1,83 @@ +const favoriteList = document.querySelector(".favorite-list"); +const localStorage = window.localStorage; +const mySearchResults = document.createElement("button"); +const body=document.querySelector("body"); +let localData = JSON.parse(localStorage.getItem("favList")); + +mySearchResults.innerHTML = "Go to search results"; +body.insertBefore(mySearchResults, favoriteList); + +mySearchResults.addEventListener("click", function(event) { + event.preventDefault(); + window.location.href = "movie.html"; +}); + + +function loadFavList(localData) { + + favoriteList.innerHTML=" "; + localData.sort(function(a, b) { + return a.index - b.index; + }) + .map(movie => { + let movieContainer = document.createElement("li"); + movieContainer.className = "movie"; + movieContainer.id = movie.id; + movieContainer.value = movie.index; + + // console.log(movie.index) + // console.log(movie.title) + // console.log(movieContainer.value) + + let movieTitle = document.createElement("h2"); + let movieYear = document.createElement("h3"); + let posterImage = document.createElement("img"); + let upVote=document.createElement("button"); + let downVote=document.createElement("button"); + + movieTitle.innerHTML = `${movie.title}`; + movieTitle.className = "movie-title"; + movieContainer.appendChild(movieTitle); + + movieYear.innerHTML = `${movie.year}`; + movieYear.className = "movie-year"; + movieContainer.appendChild(movieYear); + + posterImage.src = `${movie.image}`; + posterImage.className = "movie-image"; + posterImage.alt = "Movie poster"; + movieContainer.appendChild(posterImage); + + upVote.innerHTML="Up" + movieContainer.appendChild(upVote); + + downVote.innerHTML="Down" + movieContainer.appendChild(downVote); + + upVote.addEventListener("click", function(event) { + event.preventDefault(); + localData=shift(event,1); + loadFavList(localData); + }); + downVote.addEventListener("click", function(event) { + event.preventDefault(); + localData=shift(event,-1); + loadFavList(localData); + //function; + }); + + favoriteList.appendChild(movieContainer); + return localData; + + }); +} + +loadFavList(localData); + +function shift(event,number){ + const currentIndex= event.target.parentNode.getAttribute("value"); + localData[parseInt(currentIndex)-number].index=currentIndex; + localData[currentIndex].index=parseInt(currentIndex)-number; + localStorage.setItem("favList", JSON.stringify(localData)); + return localData +} diff --git a/movie.html b/movie.html new file mode 100644 index 00000000..0340516f --- /dev/null +++ b/movie.html @@ -0,0 +1,27 @@ + + + + + + + + + + +

Movie Collectors

+
+ + +
+ + + + + + + + + diff --git a/movie.js b/movie.js new file mode 100644 index 00000000..8d194b00 --- /dev/null +++ b/movie.js @@ -0,0 +1,228 @@ +const searchSubmit = document.querySelector(".search-area-submit"); +const searchText = document.querySelector(".search-area-text"); +const searchResultList = document.querySelector(".search-result-list"); +const pagination = document.querySelector(".pagination"); +const localStorage = window.localStorage; +const favMovieObj = JSON.parse(localStorage.getItem("favList")) || []; +const myFavorite = document.createElement("button"); +const body = document.querySelector("body"); +const preview=document.querySelector(".preview"); +let localData; +let totalSearchResults = 0; +let n= 0; +if (JSON.parse(localStorage.getItem("favList"))!==null){ + n=JSON.parse(localStorage.getItem("favList")).length +} +//this is to insert search results using dom +function searchResult(body) { + body.Search.forEach(movie => { + let searchResultContainer = document.createElement("li"); + searchResultContainer.className = "movie"; + searchResultContainer.id = movie.imdbID; + let searchResultTitle = document.createElement("h2"); + let searchResultYear = document.createElement("h3"); + let posterImage = document.createElement("img"); + + searchResultTitle.innerHTML = `${movie.Title}`; + searchResultTitle.className = "movie-title"; + searchResultContainer.appendChild(searchResultTitle); + + searchResultYear.innerHTML = `${movie.Year}`; + searchResultYear.className = "movie-year"; + searchResultContainer.appendChild(searchResultYear); + + posterImage.src = movie.Poster; + posterImage.className = "movie-image"; + posterImage.alt = "Movie poster"; + searchResultContainer.appendChild(posterImage); + + let favorite = document.createElement("button"); + favorite.innerHTML = "Add to favorites"; + favorite.addEventListener("click", function(event) { + const favoriteMovie = event.target.parentNode.childNodes; + const movieId = event.target.parentNode.getAttribute("id"); + + console.log(favoriteMovie); + + const movieObj = { + id: movieId, + index: n, + title: favoriteMovie[0].textContent, + year: favoriteMovie[1].textContent, + image: favoriteMovie[2].getAttribute("src") + // details:favoriteMovie[4].textContent + }; + n++; + favMovieObj.push(movieObj) ; + localStorage.setItem("favList", JSON.stringify(favMovieObj)); + console.log(localStorage); + }); + + searchResultContainer.appendChild(favorite); + searchResultList.appendChild(searchResultContainer); + }); +} + +function pages(body, page) { + totalSearchResults = body.totalResults; + let totalPage = totalSearchResults / 10; + + pagination.innerHTML = " "; + for (i = page; i < totalPage + 1; i++) { + button = document.createElement("button"); + button.innerHTML = `${i}`; + pagination.appendChild(button); + + if (i > parseInt(page) + 8) { + button.className = "page-button hide-button"; + } else { + button.className = "page-button"; + } + } + + if (totalPage > 9 && parseInt(page) < totalPage - 8) { + const morePage = document.createElement("button"); + morePage.innerHTML = `...`; + morePage.className = "more-page"; + console.log(morePage); + pagination.appendChild(morePage); + } +} + +//to fetch search results from API +function loadAPI(page) { + const keyWord = searchText.value; + const url = `http://www.omdbapi.com/?s=${keyWord}&apikey=d2807699&page=${page}`; + searchResultList.innerHTML = " "; + fetch(url) + .then(response => response.json()) + .then(body => { + if (typeof body.Search === "undefined") { + pagination.innerHTML = " "; + const errorMessage = document.createElement("p"); + errorMessage.innerHTML = `No results available`; + searchResultList.appendChild(errorMessage); + } else { + searchResult(body); + pages(body, page); + } + }) + .catch(error => { + console.log(error); + }); +} + +searchSubmit.addEventListener("click", function(event) { + event.preventDefault(); + let page = 1; + loadAPI(page); +}); + + + +pagination.addEventListener("click", event => { + event.preventDefault(); + page = event.target.textContent; + loadAPI(page); +}); + +//clear all results once the search box is cleared +searchText.addEventListener("input", event => { + event.preventDefault(); + if (searchText.value === "") { + preview.innerHTML = ""; + pagination.innerHTML = ""; + } + if (searchText.value.length>=3){ + //console.log(searchText.value) + const keyWord = searchText.value; + const url = `http://www.omdbapi.com/?s=${keyWord}&apikey=d2807699`; + preview.innerHTML=" "; + fetch(url) + .then(response => response.json()) + .then(body => { + if (typeof body.Search !== "undefined") { + let arr=body.Search; + let prev0=document.createElement("li"); + let prev1=document.createElement("li"); + let prev2=document.createElement("li"); + prev0.innerHTML=`${arr[0].Title} ${arr[0].Year}` + prev1.innerHTML=`${arr[1].Title} ${arr[1].Year}` + prev2.innerHTML=`${arr[2].Title} ${arr[2].Year}` + preview.appendChild(prev0); + preview.appendChild(prev1); + preview.appendChild(prev2); + } + }) + .catch(error => { + console.log(error); + }); + } +}); + +//this is to add movie details using dom +function movieDetails(body, id) { + const searchResultDetails = document.createElement("div"); + searchResultDetails.className = "search-result-details"; + + let movieDirector = document.createElement("p"); + movieDirector.innerHTML = `Director: ${body.Director}`; + searchResultDetails.appendChild(movieDirector); + + let movieActors = document.createElement("p"); + movieActors.innerHTML = `Actors: ${body.Actors}`; + searchResultDetails.appendChild(movieActors); + + let movieCountry = document.createElement("p"); + movieCountry.innerHTML = `Country: ${body.Country}`; + searchResultDetails.appendChild(movieCountry); + + let movieRatings = document.createElement("p"); + const ratings = body.Ratings.map(rating => { + return `
${rating.Source}: ${rating.Value}`; + }); + + movieRatings.innerHTML = `Ratings: ${ratings}`; + searchResultDetails.appendChild(movieRatings); + + let moviePlot = document.createElement("p"); + moviePlot.innerHTML = `Description: ${body.Plot}`; + searchResultDetails.appendChild(moviePlot); + + document.getElementById(id).appendChild(searchResultDetails); +} + +//to fetch more details about the clicked movie from the API +if (searchResultList !== null) { + searchResultList.addEventListener("click", function(event) { + event.preventDefault(); + //remove other movie details (loaded from previous click) + const otherDetails = document.querySelector(".search-result-details"); + if (otherDetails !== null) { + otherDetails.parentNode.removeChild(otherDetails); + } + + const id = event.target.parentNode.getAttribute("id"); + const url = `http://www.omdbapi.com/?i=${id}&plot=full&apikey=d2807699`; + + if (event.target.parentNode.lastChild.matches("button")) { + fetch(url) + .then(response => response.json()) + .then(body => { + movieDetails(body, id); + }) + .catch(error => console.log(error)); + } + }); +} + +myFavorite.innerHTML = "Go to my favorites"; + + +body.insertBefore(myFavorite, pagination); + + +myFavorite.addEventListener("click", function(event) { + event.preventDefault(); + window.location.href = "favorite.html"; +}); diff --git a/style.css b/style.css new file mode 100644 index 00000000..0390bab7 --- /dev/null +++ b/style.css @@ -0,0 +1,141 @@ +*{ + all:unset; + font-family: medium-content-sans-serif-font,-apple-system,BlinkMacSystemFont, + "Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell, + "Open Sans","Helvetica Neue",sans-serif; + display: flex; + flex-direction: column; + /*justify-content: center; + align-items: center;*/ + padding: 0 10px; + position: relative; + border:0; + color: #495060; + +} + +h1{ + align-items: center; + font-size:2.618em; + height:8%; + padding: 1em 0em 0.5em 0; + +} + +h2,h3{ + align-items: center; + font-size:1em; + font-weight: bold; + font-style: italic; + height:5%; + padding-bottom : 0.5em; +} + +img{ + padding-bottom: 0.5em; +} + + +.search-area{ + display: grid; + grid-template-columns: 40% 40% 20%; + grid-template-rows: 10% 10%; + align-items: center; + justify-content: center; + padding-bottom: 1em; + +} + +.search-area-text{ + border-bottom: 2px solid #FDF4A0; + padding:5px 5px; + + height: 40%; + font-size: 1.5em; + grid-column:1/3; + +} + +input.search-area-submit{ + background-image: url("button.png"); + background-position: 0px 0px; + background-repeat: no-repeat; + background-size:contain; + + height: 50px; + /*width: 50px;*/ + border: 0px; + cursor: pointer; + outline: 0; + grid-column: 3/4; +} + + + + + +.pagination{ + display:grid; + + grid-template-columns: repeat(10,8pw); + align-items: center; + justify-content: center; + grid-gap: 0.25em; + margin:0; + padding-bottom: 1em; +} + +.page-button{ + + + border-radius: 100%; + background-color: #FDF4A0; +} + + +.more-page{ + grid-column: 10/11; +} + +.hide-button{ + display: none; +} + +.search-result-list, .favorite-list{ + display: grid; + grid-gap: 1em; + grid-template-columns: 99%; + grid-template-rows: repeat(minmax(200px,1fr)); + justify-content: start; + align-items: start; + +} + +.movie{ + background-color: #FBFAFA; + padding: 1em 0.5em 1em 0.5em; + justify-content: flex-start; + +} + +p{ + padding-bottom: 0.3em; +} + +@media(min-width:768px) { + .search-result-list,.favorite-list{ + grid-template-columns: 50% 50%; + } +} + +@media(min-width:960px) { + .search-result-list,.favorite-list{ + grid-template-columns: 33% 33% 33%; + } +} + +@media(min-width:1200px) { + .search-result-list,.favorite-list{ + grid-template-columns: 20% 20% 20% 20% 20%; + } +}