I'm creating a web site dedicated to find films as convenient as possible, using TMDB service. Currently, I'm trying to make a search, using just an actor's name. I've learned a lot about TMDB and all its features. Eventually, I've created code that should have found movies starring with entered actor, yet it doesn't. Moreover, when I type any actor's name, I just see an option of film, which is 'Undefined' (sometimes there are two films). I'll attach my code and I'd be more than grateful for any suggestions or explanations of people, who already had their own experience with TMDB. Thanks for time spent for reading this article. Have a good day!
HTML:
<div class="actor-form" id="actorForm">
<input type="text" class="actor-form-input" placeholder="Desired Actor:" name="name" id='actorNameInput'
autocomplete="off">
<label for="actorNameInput" class="actor-form-label">
<span class="actor-form-span">Desired Actor</span>
</label>
</div>
<main id="main"></main>
CSS:
.actor-form {
position: absolute;
padding: 10px 0 0 0;
width: 40%;
left: 50%;
top: 35%;
transform: translate(500%, -50%);
transition: all 2s;
cursor: default;
}
.actor-form-input {
font-family: 'Marvel', sans-serif;
width: 100%;
border: 0;
border-bottom: 2px solid #202020;
outline: 0;
font-size: 26px;
color: #202020;
padding: 7px 0;
background: transparent;
transition: border-color 0.4s;
}
.actor-form-input::placeholder {
color: transparent;
}
.actor-form-input:placeholder-shown+.actor-form-label {
font-size: 26px;
cursor: text;
top: 20px;
}
.actor-form-label {
position: absolute;
top: -20px;
display: block;
transition: 0.2s;
color: #202020;
font-size: 24px;
font-family: 'Marvel', sans-serif;
}
.actor-form-input:focus+.actor-form-label {
position: absolute;
top: -20px;
display: block;
transition: 0.2s;
color: #21ebff;
font-size: 24px;
}
.actor-form-input:focus {
border-bottom: 2px solid #21ebff;
}
.actor-form-span {
-webkit-user-select: none;
user-select: none;
}
JS:
// Show Movies
const API_KEY = 'api_key= [here is my apikey]';
const BASE_URL = 'https://api.themoviedb.org/3';
const API_URL = BASE_URL + '/discover/movie?sort_by=popularity.desc&' + API_KEY;
const IMG_URL = 'https://image.tmdb.org/t/p/w500';
const searchURL = BASE_URL + '/search/movie?' + API_KEY;
const searchPersonURL = BASE_URL + '/search/person?' + API_KEY;
getMovies(API_URL);
function getMovies(url) {
lastUrl = url;
fetch(url).then(res => res.json()).then(data => {
if (data.results.length !== 0) {
showMovies(data.results);
currentPage = data.page;
nextPage = currentPage + 1;
prevPage = currentPage - 1;
totalPages = data.total_pages;
current.innerText = currentPage;
if (currentPage <= 1) {
prev.classList.add('disabled');
next.classList.remove('disabled')
} else if (currentPage >= totalPages) {
prev.classList.remove('disabled');
next.classList.add('disabled')
} else {
prev.classList.remove('disabled');
next.classList.remove('disabled')
}
tagsEl.scrollIntoView({ behavior: 'smooth' })
} else {
main.innerHTML = `<h1 class="no-results">No Results Found</h1>`
}
})
}
function showMovies(data) {
main.innerHTML = '';
data.forEach(movie => {
const { title, poster_path, vote_average, overview, id } = movie;
const movieEl = document.createElement('div');
movieEl.classList.add('movie');
movieEl.innerHTML = `
<img src="${poster_path ? IMG_URL + poster_path : "http://via.placeholder.com/1080x1580"}" alt="${title}">
<div class="movie-info">
<h3>${title}</h3>
</div>
<div class="overview">
<h3>Overview</h3>
${overview}
<br/>
<button class="know-more" id="${id}">Know More</button
</div>
`
main.appendChild(movieEl);
document.getElementById(id).addEventListener('click', () => {
console.log(id)
openNav(movie)
})
})
}
// Actor Form
const actorFormLabel = document.querySelector('.actor-form-label');
const actorNameInput = document.getElementById('actorNameInput');
actorNameInput.addEventListener('input', function (e) {
let val = e.target.value.trim();
if (val.length) {
getMovies(searchPersonURL + '&query=' + encodeURI(val));
console.log(encodeURI(val));
}
});
Related
I am trying to recreate netflix kind of carousel ui using this wds tutorial (https://www.youtube.com/watch?v=yq4BeRtUHbk) for movie cast details using Reactjs and running through a problem.
I am fetching data from moviedb database and trying to achieve netflix like carousel effect.
the problem is when i click button for changing the slider index using getComputedStyle in css,
I get entire div recreated again several times.
I Fetch the data in the MovieDetails component and pass it to MovieDetailsPage component
export default function MovieDetails() {
const [MovieDetail, setMovieDetail] = useState([])
const [CastDetails, setCastDetails] = useState([])
const { id } = useParams();
const API_Key = '4ee812b6fb59e5f8fc44beff6b8647ed';
console.log('this is id', id);
useEffect(() => {
getDetail();
getCastDetails();
console.log('main');
}, [id])
const getDetail = useCallback(() => {
fetch(`https://api.themoviedb.org/3/movie/${id}?api_key=${API_Key}&language=en-US`)
.then(res => res.json())
.then(data => {
console.log(data, 'data');
setMovieDetail(data)
})
}, [id])
const getCastDetails = useCallback(() => {
fetch(`https://api.themoviedb.org/3/movie/${id}/credits?api_key=${API_Key}&language=en-
US`)
.then(res => res.json())
.then(data => {
setCastDetails(data.cast)
}
)
console.log('get cast details rendered');
}, [id])
useEffect(() => {
console.log(MovieDetail, 'Moviedetils')
}, [MovieDetail])
return (
<div>
<MoviesDetailsPage {...MovieDetail} CastDetails={CastDetails} API_Key={API_Key} />
</div>
)
}
MovieDetailsPage.jsx
export default function MoviesDetailsPage({ id, poster_path, backdrop_path, API_Key,
CastDetails }) {
const API_image = 'https://image.tmdb.org/t/p/w500/';
document.addEventListener('click', e => {
e.preventDefault();
let handle;
if(e.target.matches(".handle")){
handle = e.target
}else{
handle = e.target.closest(".handle")
}
if(handle != null) onHandleClick(handle)
})
function onHandleClick(handle){
const slider = handle.closest(".MovieCastContainer").querySelector(".slider")
console.log(slider, 'sliderindex')
const sliderIndex = parseInt(getComputedStyle(slider).getPropertyValue("--slider-index"))
if(handle.classList.contains("left-handle")){
slider.style.setProperty("--slider-index", sliderIndex - 1)
}
if(handle.classList.contains("right-handle")){
slider.style.setProperty("--slider-index", sliderIndex + 1)
}
}
const castInfo = CastDetails && CastDetails.map(data => <img src={API_image+data.profile_path}
alt={data.name} />)
console.log(CastDetails, 'Castdetails')
return (
<div className="MovieDetailsPageCont">
<div className='MovieDetailsContainer'>
<div className="headerImg"><img src={API_image + backdrop_path}
alt='backdrop_path' style={{ width: '100%', borderRadius: '10px' }} /></div>
<div className="movieid">{id}</div>
</div>
<div className='MovieCastContainer'>
<button className="handle left-handle">
<div className="text">‹</div>
</button>
<div className='slider'>
{
castInfo ? castInfo : '...Loading'
}
</div>
<button className="handle right-handle">
<div className="text">›</div>
</button>
</div>
</div>
)
}
My css page where change the slider index to transfrom to next set of values
*, *::after, *::before{
box-sizing: border-box;
}
:root{
--slider-padding: 5rem;
}
.MovieCastContainer{
display: flex;
justify-content: center;
overflow: hidden;
}
.slider{
--slider-index: 0;
display: flex;
flex-grow: 1;
margin: 0 .25rem;
transform: translateX(calc(var(--slider-index) * -100%));
transition: transform 250ms ease-in-out;
}
.slider > img {
flex: 0 0 25%;
max-width: 25%;
aspect-ratio: 16 / 9;
padding: .25rem;
border-radius: 1rem;
overflow: hidden;
}
.handle{
border: none;
border-radius: 1rem;
flex-grow: 0;
background-color: rgba(0, 0, 0, .25);
z-index: 10;
margin: .25rem 0;
padding: 0 .5rem;
cursor: pointer;
font-size: 5rem;
display: flex;
align-items: center;
justify-content: center;
color: white;
line-height: 0;
transition: font-size 150ms ease-in-out;
}
.left-handle{
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.right-handle{
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.handle:hover,
.handle:focus {
background-color: rgba(0, 0, 0, .5);
}
.handle:hover .text,
.handle:focus .text{
transform: scale(1.2)
}
Everytime I click next button in ui the I get this
please if you can help out with this any help would be appreciated.
I have a couple of buttons which I'm trying to loop through and add a event listener function to them. I do this by using the following code:
function handleButtonClick({
target
}) {
const btnNode = target.closest('button');
console.log('hi')
}
const btns = document.querySelectorAll('.answerBtn');
btns.forEach(btns => {
btns.addEventListener('click', handleButtonClick)
})
Everytime a button is clicked it should run the function and it does. The only problem is that if I have 4 buttons, it runs the first button 4 time, the 2th button 3 times, the 3th button 2 times and the last ones. I know this happens because of the loop, but is there a way to make sure that it doesn't matter on what button I press, it only runs the function once?
let clickCountStorage = new Map;
class Question {
/**
* #param {string} question
* #param {string} prefix
* #param {string} description
* #param {string} display
* #param {string} answerType
* #param {string} multiSelect
* #param {Answer[]} answers
*/
constructor(question = "", prefix = "", description = "", display = "", answerType = "", multiSelect = "",
answers = []) {
this.question = question;
this.prefix = prefix;
this.description = description;
this.display = display;
this.answerType = answerType;
this.multiSelect = multiSelect;
this.answers = answers;
}
}
class Answer {
constructor(id = "", name = "") {
this.id = id;
this.name = name;
}
}
function createButton(id) {
let generateNewAnswer = document.createElement('button');
generateNewAnswer.setAttribute('type', 'button');
generateNewAnswer.id = `answerBtn${ id }`;
generateNewAnswer.classList.add('answerBtn');
generateNewAnswer.innerHTML = 'Add Answer';
return generateNewAnswer
}
function main() {
function handleButtonClick() {
let target = event.target;
const btnNode = target.closest('button');
const buttonClickTotal = clickCountStorage.get(btnNode) + 1;
clickCountStorage.set(btnNode, buttonClickTotal);
const clickCountList = Array
.from(
clickCountStorage.values()
);
const allButtonsClickTotal = clickCountList
.reduce((total, count) => total + count, 0);
const AllBtnsClickedThreeTimes = clickCountList
.every(count => count >= 3);
console.log({
buttonClickTotal,
allButtonsClickTotal,
AllBtnsClickedThreeTimes,
});
}
const btns = document.querySelectorAll('.answerBtn');
console.log(btns)
btns.forEach((btn) => {
btn.addEventListener('click', handleButtonClick);
console.log(btn)
})
}
class Structure {
constructor() {
/**
* #type {Question[]}
*/
this.questions = [];
this.clickCount = 0;
this.currentQuestion = this.questions.length;
this.displayArr = ["Selecteer soort:", "button", "colorBtn", "position", "dropdown"];
this.typeArr = ["Selecteer type:", "max", "all"];
this.MultiArr = ["Multiselect:", "true", "false"];
}
AddQuestion() {
let currentQuestion = this.questions.length;
// The new created question which has to be added to the questions.
let newQuestion = new Question();
// Push the new question to the question list.
this.questions.push(newQuestion);
// The div generator for the answers
let answerDIV = document.createElement('div');
answerDIV.className = 'answerDIV' + currentQuestion;
answerDIV.id = 'AnswerDivId' + currentQuestion;
document.getElementsByClassName('create')[0].appendChild(answerDIV);
let generateNewAnswer = createButton(currentQuestion);
clickCountStorage.set(generateNewAnswer, 0)
generateNewAnswer.onclick = _ => this.AddAnswer(currentQuestion);
document.getElementsByClassName('create')[0].appendChild(generateNewAnswer);
}
/**
* #param {int} workingQuestionIndex
*/
AddAnswer(workingQuestionIndex) {
let workingQuestion = this.questions[workingQuestionIndex];
let newAnswerIndex = workingQuestion.answers.length;
let newAnswerId = 'id' + newAnswerIndex;
// The new answer to insert.
let newAnswer = new Answer(newAnswerId);
// Add the new answer to the total answers.
workingQuestion.answers.push(newAnswer);
let idElement = document.createElement('input');
idElement.setAttribute('type', 'text');
idElement.name = "id";
idElement.id = newAnswerId;
idElement.classList.add('id', 'QuestionNumber' + workingQuestionIndex);
idElement.placeholder = 'Add the ID of the answer';
idElement.addEventListener('input', function(_) {
newAnswer.id = this.value;
});
// Appends the answers to the AnswerDiv
document.getElementsByClassName('answerDIV' + workingQuestionIndex)[0].appendChild(idElement);
}
}
class GenerateArray {
constructor() {
this.structure = new Structure();
}
generateQuestionPart() {
this.structure.AddQuestion();
}
}
let newQuestion = new Question();
let struc = new Structure();
NewArray = new GenerateArray();
document.querySelectorAll('.QPB')[0].addEventListener('click', () => {
main()
})
body {
margin: 0;
padding: 0;
}
.container {
height: 1000px;
width: 800px;
position: relative;
margin-top: 5px;
left: 50%;
-ms-transform: translate(-50%, 5%);
transform: translate(-50%, 5%);
}
.QPB {
position: relative;
float: left;
margin-left: 15px;
margin-top: 10px;
margin-bottom: 10px;
border: none;
border-radius: 0.25rem;
color: white;
font-size: 40px;
background-color: #ff5c01!important;
width: 50px;
height: 50px;
}
.question,
.prefix,
.description {
position: relative;
margin-right: 5px;
width: 95%;
height: 30px;
margin-bottom: 10px;
margin-left: 15px;
}
.SelClassD,
.SelClassT,
.SelClassM {
position: relative;
margin-right: 5px;
width: 20%;
height: 30px;
margin-bottom: 10px;
margin-left: 15px;
border: 2px solid #ced4da;
border-radius: 0.5rem;
}
.SelClassD:focus,
.SelClassT:focus,
.SelClassM:focus {
outline: none !important;
border: 4px solid rgb(135, 206, 250, 0.5);
border-radius: 0.5rem;
}
.question,
.description,
.prefix,
.id,
.name {
border: 2px solid #ced4da;
border-radius: 0.5rem;
}
.question:focus,
.description:focus,
.prefix:focus,
.id:focus,
.name:focus {
outline: none !important;
border: 4px solid rgb(135, 206, 250, 0.5);
border-radius: 0.5rem;
}
.id,
.name {
position: relative;
width: 90%;
height: 30px;
margin-bottom: 10px;
margin-left: 55px;
}
.answerBtn {
width: 100px;
height: 40px;
background-color: #ff5c01!important;
color: white;
border: none;
border-radius: 0.25rem;
margin-bottom: 50px;
margin-left: 15px;
}
.CreateArray {
width: 95%;
margin-left: 20px;
margin-bottom: 10px;
height: 40px;
background-color: #3db556!important;
color: white;
border: none;
border-radius: 0.25rem;
}
/* card */
.DONOT {
margin-left: 15px;
font-style: italic;
font-size: 24px;
}
.card-body-home {
border: 2px solid rgba(0, 0, 0, .125);
border-bottom: none;
border-radius: 3px 3px 0 0;
}
/* form card */
.form-card-DT {
max-width: 800px;
border: none!important;
height: 100%;
/* padding-bottom: 10px; */
}
.form-card-header {
border: none;
background-color: #ff5c01!important;
color: white;
font-weight: 500;
font-style: italic;
font-size: 24px;
padding-top: 10px;
padding-left: 15px;
border-radius: 0!important;
height: 35px;
}
.form-card-body {
border-radius: 0;
border: solid 1px #b5b5b5;
border-top: none;
}
<div style='width: 1000px;margin: auto;'>
<div class='card text-dark bg-light mb-3 form-card-DT'>
<div class='card-header form-card-header'>Creeër een vragenlijst:</div>
<div class='card-body form-card-body'>
<div class="DONOT">Gebruik het volgende teken niet ivm error: '</div>
<div class="create">
<button id="QuestionPartBtn" class="QPB" type="button" onclick="NewArray.generateQuestionPart()">+
</button>
<br><br>
</div>
<div class="result">
<button id="download-btn" class="CreateArray">Generate Code</button>
</div>
</div>
</div>
</div>
You can also try to use Event Bubbling and assign only one "click" event listener to the closest common parent
I encountered 2 problems regarding my todo app. I am just coding along from one of the Youtube tutorials to improve my Javascript knowledge. I cannot delete the last remaining li in my todo list but the local storage shows it is already deleted. Same as my clear all button, the local storage is already been deleted but my li's are still showing. Aside from that, after I click clear all button, I cannot delete the li's one by one even if it's still showing(my guess is the local storage is already empty and there is no more data to be deleted). Thank you for the future responses!
const inputBox = document.querySelector('.input-container input')
const addBtn = document.querySelector('.input-container button')
const todo = document.querySelector('.todo-list')
const deleteAllBtn = document.querySelector('.footer button')
// input button
inputBox.onkeyup = () => {
let userData= inputBox.value;
if (userData.trim() != 0 ) {
addBtn.classList.add('active')
} else {
addBtn.classList.remove('active');
}
}
showTask();
//adding it to local storage
addBtn.onclick = () => {
let userData = inputBox.value;
let getLocalStorage = localStorage.getItem("New Todo")
if (getLocalStorage == null) {
listArr = [];
} else {
listArr = JSON.parse(getLocalStorage);
}
listArr.push(userData);
localStorage.setItem("New Todo", JSON.stringify(listArr));
showTask();
addBtn.classList.remove('active');
}
//add task
function showTask() {
let getLocalStorage = localStorage.getItem("New Todo")
if (getLocalStorage == null) {
listArr = [];
} else {
listArr = JSON.parse(getLocalStorage);
}
const pendingNumb = document.querySelector('.pending');
pendingNumb.textContent= listArr.length;
let newLiTag = '';
listArr.forEach((element, index) => {
newLiTag += `<li> ${element} <span onclick="deleteTask(${index})";><i class="fas fa-trash-alt"></i></span></li>
`;
todo.innerHTML = newLiTag;
inputBox.value = '';
});
}
//delete task
function deleteTask(index) {
let getLocalStorage = localStorage.getItem("New Todo");
listArr = JSON.parse(getLocalStorage);
listArr.splice(index, 1);
localStorage.setItem("New Todo", JSON.stringify(listArr));
showTask();
}
//delete all task
deleteAllBtn.onclick = () => {
listArr = [];
localStorage.setItem("New Todo", JSON.stringify(listArr));
showTask();
}
.todo-list {
margin: 10px 0;
width: 360px;
height: 260px;
max-height: 250px;
overflow-y: auto;
}
.todo-list li {
position: relative;
list-style-type: none;
background: #f2f2f2;
margin: 5px 0;
display: flex;
justify-content: space-between;
padding: 5px 5px;
overflow: hidden;
}
.todo-list li span {
position: absolute;
right: -30px;
top: 0;
background: #d00000;
color: #fff;
width: 35px;
height: 35px;
padding-top: 5px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
}
.todo-list li:hover span {
right: 0;
}
.footer {
font-size: 17px;
font-weight: bold;
margin: 0 10px;
}
.footer span {
margin: 0 10px;
}
.footer button {
height: 2.5rem;
width: 5rem;
font-size: 1rem;
color: #fff;
background: #d00000;
border: none;
cursor: pointer;
}
<div class="container">
<h1>TODO list</h1>
<div class="input-container">
<input type="text" class="input" placeholder="Input Text Here">
<button><i class="fas fa-plus"></i></button>
</div>
<ul class="todo-list">
</ul>
<div class="footer">
<span>You have<span class="pending">0</span>pending task left</span>
<button>Clear All</button>
</div>
you should update todo element outside of forEach
function showTask() {
let getLocalStorage = localStorage.getItem("New Todo")
if (getLocalStorage == null) {
listArr = [];
} else {
listArr = JSON.parse(getLocalStorage);
}
const pendingNumb = document.querySelector('.pending');
pendingNumb.textContent = listArr.length;
let newLiTag = '';
listArr.forEach((element, index) => {
newLiTag += `<li> ${element} <span onclick="deleteTask(${index})";><i class="fas fa-trash-alt"></i></span></li>
`;
});
//move it here
todo.innerHTML = newLiTag;
inputBox.value = '';
}
I'm looking for advice/tips on how to fix a function that is supposed to remove items from localStorage. I'm following a tutorial by John Smilga that I found on Youtube. Although I've modeled my code on his, apparently, I have missed something.
This function works perfectly well if I run it manually from the console and pass in the id of the item that I want to remove from localStorage.
function removeFromLocalStorage(id) {
console.log(id);
let storageItems = getLocalStorage();
console.log(storageItems);
storageItems = storageItems.filter(function(singleItem) {
if (singleItem.id !== id) {
return singleItem;
}
})
console.log(storageItems);
localStorage.setItem("list", JSON.stringify(storageItems));
}
However, when this function is triggered by the deleteItem() function, it refuses to remove the item from localStorage. It still works, there are a bunch of console.logs in it that track its execution, and I can check that it receives the correct item id as the argument, but for some reason it doesn't filter out the item that needs to be removed. I am completely lost and have no idea how to identify the problem. I can't debug it with console.logs as I usually do. I will be very grateful if you help me find the problem. Any advice will be appreciated.
In case the entire code is needed, please find it below.
const form = document.querySelector(".app__form");
const alert = document.querySelector(".app__alert");
const input = document.querySelector(".app__input");
const submitBtn = document.querySelector(".app__submit-btn");
const itemsContainer = document.querySelector(".app__items-container");
const itemsList = document.querySelector(".app__items-list");
const clearBtn = document.querySelector(".app__clear-btn");
let editElement;
let editFlag = false;
let editId = "";
form.addEventListener("submit", addItem);
clearBtn.addEventListener("click", clearItems);
function addItem(e) {
e.preventDefault();
const id = Math.floor(Math.random() * 9999999999);
if (input.value && !editFlag) {
const item = document.createElement("div");
item.classList.add("app__item");
const attr = document.createAttribute("data-id");
attr.value = id;
item.setAttributeNode(attr);
item.innerHTML = `<p class='app__item-text'>${input.value}</p>
<div class='app__item-btn-cont'>
<button class='app__item-btn app__item-btn--edit'>edit</button>
<button class='app__item-btn app__item-btn--delete'>delete</button>
</div>`
const editBtn = item.querySelector(".app__item-btn--edit");
const deleteBtn = item.querySelector(".app__item-btn--delete");
editBtn.addEventListener("click", editItem);
deleteBtn.addEventListener("click", deleteItem);
itemsList.appendChild(item);
displayAlert("item added", "success");
addToLocalStorage(id, input.value);
setBackToDefault();
itemsContainer.classList.add("app__items-container--visible");
} else if (input.value && editFlag) {
editElement.textContent = input.value;
// edit local storage
editLocalStorage(editId, input.value);
setBackToDefault();
displayAlert("item edited", "success");
} else {
displayAlert("empty field", "warning");
}
}
function setBackToDefault() {
input.value = "";
editFlag = false;
editId = "";
submitBtn.textContent = "Submit";
submitBtn.className = "app__submit-btn";
}
function displayAlert(text, action) {
alert.textContent = text;
alert.classList.add(`app__alert--${action}`);
setTimeout(function() {
alert.textContent = "";
alert.classList.remove(`app__alert--${action}`);
}, 700)
}
function clearItems() {
const items = document.querySelectorAll(".app__item");
if (items.length > 0) {
items.forEach(function(singleItem) {
itemsList.removeChild(singleItem);
})
itemsContainer.classList.remove("app__items-container--visible");
displayAlert("items cleared", "cleared");
setBackToDefault();
}
}
function editItem(e) {
const item = e.currentTarget.parentElement.parentElement;
editElement = e.currentTarget.parentElement.previousElementSibling;
editId = item.dataset.id;
editFlag = true;
input.value = editElement.textContent;
submitBtn.textContent = "Edit";
submitBtn.classList.add("app__submit-btn--edit");
input.focus();
}
function deleteItem(e) {
const item = e.currentTarget.parentElement.parentElement;
const itemId = item.dataset.id;
removeFromLocalStorage(itemId);
displayAlert("item removed", "cleared");
setBackToDefault();
itemsList.removeChild(item);
if (itemsList.children.length === 0) {
itemsContainer.classList.remove("app__items-container--visible");
}
}
function addToLocalStorage(id, value) {
const itemsObj = {id: id, value: input.value};
let storageItems = getLocalStorage();
storageItems.push(itemsObj);
localStorage.setItem("list", JSON.stringify(storageItems));
}
function removeFromLocalStorage(id) {
console.log(id);
let storageItems = getLocalStorage();
console.log(storageItems);
storageItems = storageItems.filter(function(singleItem) {
if (singleItem.id !== id) {
return singleItem;
}
})
console.log(storageItems);
localStorage.setItem("list", JSON.stringify(storageItems));
}
function editLocalStorage(id, value) {
}
function getLocalStorage() {
return localStorage.getItem("list") ? JSON.parse(localStorage.getItem("list")) : [];
}
* {
margin: 0;
padding: 0;
}
.app {
width: 70%;
max-width: 600px;
margin: 75px auto 0;
}
.app__title {
text-align: center;
/* color: #1B5D81; */
margin-top: 20px;
color: #377FB4;
}
.app__alert {
width: 60%;
margin: 0 auto;
text-align: center;
font-size: 20px;
color: #215884;
border-radius: 7px;
height: 23px;
transition: 0.4s;
text-transform: capitalize;
}
.app__alert--warning {
background-color: rgba(243, 117, 66, 0.2);
color: #006699;
}
.app__alert--success {
background-color: rgba(165, 237, 92, 0.4);
color: #3333ff;
}
.app__alert--cleared {
background-color: #a978da;
color: white;
}
.app__input-btn-cont {
display: flex;
margin-top: 30px;
}
.app__input {
width: 80%;
box-sizing: border-box;
font-size: 20px;
padding: 3px 0 3px 10px;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
border-right: none;
border: 1px solid #67B5E2;
background-color: #EDF9FF;
}
.app__input:focus {
outline: transparent;
}
.app__submit-btn {
display: block;
width: 20%;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
border-left: none;
background-color: #67B5E2;
border: 1px solid #67B5E2;
cursor: pointer;
font-size: 20px;
color: white;
transition: background-color 0.7s;
padding: 3px 0;
}
.app__submit-btn--edit {
background-color: #95CB5D;
}
.app__submit-btn:active {
width: 19.9%;
padding: 0 0;
}
.app__submit-btn:hover {
background-color: #377FB4;
}
.app__submit-btn--edit:hover {
background-color: #81AF51;
}
.app__items-container {
visibility: hidden;
/* transition: 0.7s; */
}
.app__items-container--visible {
visibility: visible;
}
.app__item {
display: flex;
justify-content: space-between;
margin: 20px 0;
}
.app__item:hover {
background-color: #b9e2fa;
border-radius: 10px;
}
.app__item-text {
padding-left: 10px;
font-size: 20px;
color: #1B5D81;
}
.app__item-btn-cont {
display: flex;
}
.app__item-btn-img {
width: 20px;
height: 20px;
}
.app__item-btn {
border: none;
background-color: transparent;
cursor: pointer;
display: block;
font-size: 18px;
}
.app__item-btn--edit {
margin-right: 45px;
color: #2c800f;
}
.app__item-btn--delete {
margin-right: 15px;
color: rgb(243, 117, 66);
}
.app__clear-btn {
display: block;
width: 150px;
margin: 20px auto;
border: none;
background-color: transparent;
font-size: 20px;
color: rgb(243, 117, 66);
letter-spacing: 2px;
cursor: pointer;
transition: border 0.3s;
border: 1px solid transparent;
}
.app__clear-btn:hover {
border: 1px solid rgb(243, 117, 66);
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" name="viewport" content="width=device-width,
initial-scale=1">
<link rel="stylesheet" href="list.css">
<title>To Do List App</title>
</head>
<body>
<section class="app">
<form class="app__form">
<p class="app__alert"></p>
<h2 class="app__title">To Do List</h2>
<div class="app__input-btn-cont">
<input class="app__input" type="text" id="todo" placeholder="do stuff">
<button class="app__submit-btn">Submit</button>
</div>
</form>
<div class="app__items-container">
<div class="app__items-list">
</div>
<button class="app__clear-btn">Clear Items</button>
</div>
</section>
<script src="list.js"></script>
</body>
</html>
Your code is fine you just used the wrong comparison operator.
In your case you are comparing 2 IDs (operands) to see if they match up, so you should use normal operators such as (==, !=), but instead in your case, you have used strict operators which are used to compare the operand type and the operand itself.
You can learn more about Comparison Operators here.
Ultimatly,
In your function removeFromLocalStorage(id), you have an extra equal sign in your if function.
Instead of:
if (singleItem.id !== id) {
return singleItem;}
It should be:
if (singleItem.id != id) {
return singleItem;}
Hope this helps.
I’m working on a re-creation of the flare image that Stack Exchange offers, and the re-creation is more responsive in that I can hover over a site icon and show my stats for a given Stack Exchange domain. I currently have to manually update my data which I plan to do twice a month or so, unless there’s a way to load that data directly from Stack Exchange via a web service or similar.
A few things to keep in mind:
I will be hosting this in an ASP.NET web application so C# APIs would be fine.
Web services would be perfect too since I can call them from JavaScript.
I would need links to documentation for any service provided.
Below is my current manual re-creation in case you’re curious or don’t know what the SE flair is, though it does need to be cleaned up and made to be more efficient.
var siteNames = [ 'Stack Exchange',
'Puzzling',
'Stack Overflow',
'Software Engineering',
'Mathematics',
'Physical Fitness' ]
var reps = [ '6.2k', '4.3k', '954', '410', '224', '220' ];
var golds = [ '1', '0', '0', '1', '0', '0' ];
var silvers = [ '14', '7', '4', '2', '1', '0' ];
var bronzes = [ '98', '50', '20', '10', '8', '10' ];
function getSiteStats(siteID) {
document.getElementById("site-name").innerText = siteNames[siteID];
document.getElementById("rep").innerText = reps[siteID];
document.getElementById("gold").innerText = golds[siteID];
document.getElementById("silver").innerText = silvers[siteID];
document.getElementById("bronze").innerText = bronzes[siteID];
}
function resetSiteStats() {
getSiteStats(0);
}
html, body {
margin: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #6aa4ed;
background-image: linear-gradient(45deg, #6aa4ed, #141d33);
background-image: -webkit-linear-gradient(45deg, #6aa4ed, #141d33);
}
h1, h5 {
color: #fff;
font-family: Arial, Helvetica, sans-serif;
font-weight: 100;
text-align: center;
margin: 0;
}
h1 {
font-size: 10vh;
}
h5 {
margin-bottom: 10px;
}
.flair {
padding: 15px;
background-color: #fff;
border-radius: 5px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
display: flex;
}
.flair img {
width: 40px;
height: 40px;
margin: 5px;
cursor: pointer;
}
.flair .profile {
width: 175px;
height: 175px;
margin: 0;
margin-right: 15px;
box-shadow: 2px 2px 4px rgba(12,13,14,0.5);
cursor: default;
}
.flair a {
color: #37f;
text-decoration: none;
margin: 5px;
}
.flair a:hover {
color: #15a;
}
.flair ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.flair ul > li {
display: inline-block;
margin: 5px;
}
.flair p {
margin: 0;
margin-left: 5px;
}
.badge div {
display: inline-block;
height: 7px;
width: 7px;
border-radius: 50%;
transform: translateY(-3px) translateX(3px);
}
.gold {
background-color: #fc0;
}
.silver {
background-color: #ccc;
}
.bronze {
background-color: #da6;
}
<h1>Stack Exchange Flair</h1>
<h5>Not Mobile Friendly (Yet)</h5>
<h5>Hover Over Site Icons</h5>
<div class="flair">
<img class="profile" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2940219/blue.jpg" />
<div class="account">
PerpetualJ
<p id="site-name">Stack Exchange</p>
<ul>
<li><strong id="rep">6.2k</strong></li>
<li>
<div class="badge">
<div class="gold"></div>
<span id="gold">1</span>
</div>
</li>
<li>
<div class="badge">
<div class="silver"></div>
<span id="silver">14</span>
</div>
</li>
<li>
<div class="badge">
<div class="bronze"></div>
<span id="bronze">98</span>
</div>
</li>
</ul>
<ul>
<li onmouseover="getSiteStats(1);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/puzzling/img/icon-48.png"/></li>
<li onmouseover="getSiteStats(2);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png"/></li>
<li onmouseover="getSiteStats(3);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/softwareengineering/img/icon-48.png"/></li>
<li onmouseover="getSiteStats(4);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/math/img/apple-touch-icon.png"/></li>
<li onmouseover="getSiteStats(5);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/fitness/img/icon-48.png?v=f5a02f85db94"/></li>
</ul>
<p>How fast do you have to slap a chicken to cook it?</p>
</div>
</div>
Is there some way for me to call an API, web service, or similar that will allow me to pull my current stats for a given Stack Exchange site?
Also, I would prefer to not do any type of web scraping or similar. I’d prefer it come from a legitimate Stack Exchange service.
NOTE: If this belongs on meta please let me know so it can be migrated.
On-Topic: This question is considered as on-topic per the help center:
We feel the best Stack Overflow questions have a bit of source code in them, but if your question generally covers…
software tools commonly used by programmers; and is
a practical, answerable problem that is unique to software development
…then you’re in the right place to ask your question!
Given the above quote, API's are tools commonly used by programmers, and by asking if Stack Exchange has one, this question is a practical and answerable problem. However, I do believe this may have been better suited for Meta, but I am unable to migrate it.
I found out recently that Stack Exchange does offer an API for these types of things. I heavily recommend reading over their documentation for the API prior to usage. In order to accomplish the task I've asked about here, I needed to utilize the following API calls:
/users/{ids}
/users/{ids}/associated
I utilized both of these calls together to recreate the Stack Exchange flair, and just-in-case you do not know what the flair is:
To get started I wrote a simple set of methods to process my requests to the API:
function getWebServiceResponse(requestUrl, callback) {
let request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (request.status < 200 || request.status >= 400)
callback("An unexpected error occurred.");
else
callback(JSON.parse(this.response));
};
request.send();
}
function getSEWebServiceResponse(request, callback) {
let apiRoot = 'https://api.stackexchange.com/2.2/';
let key = 'key=s29XM)Eqn2x3YxhjLgFwBQ((';
if (request.indexOf('?') >= 0)
key = '&' + key;
else
key = '?' + key;
getWebServiceResponse(apiRoot + request + key, function(response) { callback(response); });
}
Here, the key is needed to help prevent throttling of too many subsequent requests:
Every application is subject to an IP based concurrent request throttle. If a single IP is making more than 30 requests a second, new requests will be dropped.
From here the implementation is pretty straight-forward and was a great learning process!
/users/{ids}
Gets the users identified in ids in {ids}.
Typically this method will be called to fetch user profiles when you have obtained user ids from some other source, such as /questions.
{ids} can contain up to 100 semicolon delimited ids.
function getAssociatedAccountDetails(userID, siteName, fullSiteName, callback) {
let url = 'users/' + userID +'?order=desc&sort=reputation&site=' + siteName;
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
let account = response.items[0];
userCard.reputation += account.reputation;
userCard.badges.gold += account.badge_counts.gold;
userCard.badges.silver += account.badge_counts.silver;
userCard.badges.bronze += account.badge_counts.bronze;
if (userCard.siteUrls.length < 7) {
var siteProfileCombo = account.link + '|<IMG>|' + fullSiteName;
siteProfileCombo = siteProfileCombo.replace('<IMG>', getSiteIcon(siteName));
userCard.siteUrls.push(siteProfileCombo);
}
if (userCard.username.length < 1)
userCard.username = account.display_name;
if (userCard.profileImageUrl.length < 1)
userCard.profileImageUrl = account.profile_image;
callback();
});
}
/users/{ids}/associated
Returns all of a user's associated accounts, given their account_ids in {ids}.
{ids} can contain up to 100 semicolon delimited ids.
function getAssociatedAccounts(accountID, callback) {
let url = 'users/' + accountID + '/associated';
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
var accounts = sortAccountsByReputation(response.items);
var accountsProcessed = 0;
for (let i = 0; i < accounts.length; i++) {
let siteName = accounts[i].site_url.replace('https://', '');
siteName = siteName.replace('.stackexchange', '');
siteName = siteName.replace('.com', '');
getAssociatedAccountDetails(accounts[i].user_id, siteName, accounts[i].site_name, function() {
if (++accountsProcessed >= accounts.length)
callback();
});
}
});
}
The Full Implementation
/* Definitions */
var CardType = { Wheel: "wheel", Card: "card", Box: "box" }
var userCard = {
username: '',
profileImageUrl: '',
reputation: 0,
badges: {
gold: 0,
silver: 0,
bronze: 0
},
siteUrls: []
}
/* Initial Calls */
var accountID = '13342919';
generateCard('user-flair-wheel', accountID, CardType.Wheel);
/* Required Events */
function showSitename(tooltipID, siteName) {
var tooltip = document.getElementById(tooltipID);
tooltip.innerHTML = siteName.replace('Stack Exchange', '');
tooltip.classList.add('active');
}
function hideSitename(tooltipID) {
document.getElementById(tooltipID).classList.remove('active');
}
/* UI Generation Functions */
function generateCard(containerid, accountid, cardType) {
getAssociatedAccounts(accountID, function() {
var className = cardType.toString().toLowerCase();
var container = document.getElementById(containerid);
container.classList.add("flair");
container.classList.add(className);
// Build the card.
addProfile(container);
addScores(container, className);
addSites(container, className);
container.innerHTML += '<div id="' + containerid +
'-tooltip" class="se-tooltip"></div>';
});
}
function addProfile(container) {
container.innerHTML += '<img class="user-image" src="' +
userCard.profileImageUrl + '"/>';
container.innerHTML += '<h1 class="username display-4">' +
userCard.username + '</h1>';
}
function addScores(container, cardType) {
var badges = '<ul class="badges">';
badges += '<li><i class="fas fa-trophy"></i> <span id="reputation-' +
cardType + '">' + userCard.reputation + '</span></li>';
badges += '<li><span id="gold-badges-' + cardType + '">' +
userCard.badges.gold + '</span></li>';
badges += '<li><span id="silver-badges-' + cardType + '">' +
userCard.badges.silver + '</span></li>';
badges += '<li><span id="bronze-badges-' + cardType + '">' +
userCard.badges.bronze + '</span></li>';
badges += '</ul>';
container.innerHTML += badges;
}
function addSites(container, cardType) {
var sites = '<ul id="sites-' + cardType + '" class="sites">';
for (var i = 0; i < userCard.siteUrls.length; i++) {
var site = '<li>';
var siteLinkSplit = userCard.siteUrls[i].split('|');
site += '<a href="' + siteLinkSplit[0] + '">';
var tooltipID = container.id +'-tooltip';
var linkElement = '<a href="' + siteLinkSplit[0] + '"';
linkElement += ' onmouseover="showSitename(\'' + tooltipID + '\',\'' + siteLinkSplit[2] + '\')"';
linkElement += ' onmouseout="hideSitename(\'' + tooltipID + '\');"';
site += linkElement + '>';
site += '<img src="' + (siteLinkSplit[1] == '<IMG>' ? '#' : siteLinkSplit[1]) + '"/></a></li>';
sites += site;
}
sites += '</ul>';
container.innerHTML += sites;
}
/* Stack Exchange API Based Functions */
function getAssociatedAccounts(accountID, callback) {
let url = 'users/' + accountID + '/associated';
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
var accounts = sortAccountsByReputation(response.items);
var accountsProcessed = 0;
for (let i = 0; i < accounts.length; i++) {
let siteName = accounts[i].site_url.replace('https://', '');
siteName = siteName.replace('.stackexchange', '');
siteName = siteName.replace('.com', '');
getAssociatedAccountDetails(accounts[i].user_id, siteName, accounts[i].site_name, function() {
if (++accountsProcessed >= accounts.length)
callback();
});
}
});
}
function getAssociatedAccountDetails(userID, siteName, fullSiteName, callback) {
let url = 'users/' + userID +'?order=desc&sort=reputation&site=' + siteName;
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
let account = response.items[0];
userCard.reputation += account.reputation;
userCard.badges.gold += account.badge_counts.gold;
userCard.badges.silver += account.badge_counts.silver;
userCard.badges.bronze += account.badge_counts.bronze;
if (userCard.siteUrls.length < 7) {
var siteProfileCombo = account.link + '|<IMG>|' + fullSiteName;
siteProfileCombo = siteProfileCombo.replace('<IMG>', getSiteIcon(siteName));
userCard.siteUrls.push(siteProfileCombo);
}
if (userCard.username.length < 1)
userCard.username = account.display_name;
if (userCard.profileImageUrl.length < 1)
userCard.profileImageUrl = account.profile_image;
callback();
});
}
/* Helper Functions */
function getSEWebServiceResponse(request, callback) {
let apiRoot = 'https://api.stackexchange.com/2.2/';
let key = 'key=s29XM)Eqn2x3YxhjLgFwBQ((';
if (request.indexOf('?') >= 0)
key = '&' + key;
else
key = '?' + key;
getWebServiceResponse(apiRoot + request + key, function(response) { callback(response); });
}
function getWebServiceResponse(requestUrl, callback) {
let request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (request.status < 200 || request.status >= 400)
callback("An unexpected error occurred.");
else
callback(JSON.parse(this.response));
};
request.send();
}
function sortAccountsByReputation(accounts) {
return accounts.sort(function(a, b) { return b.reputation - a.reputation; });
}
function getSiteIcon(siteName) {
if (siteName == "meta")
return 'https://meta.stackexchange.com/content/Sites/stackexchangemeta/img/icon-48.png';
return 'https://cdn.sstatic.net/Sites/' + siteName + '/img/apple-touch-icon.png';
}
/* Flair Styles */
.flair {
position: relative;
margin: 15px;
}
.flair > .se-tooltip {
position: absolute;
left: 50%;
transform: translate(-50%);
width: 250px;
bottom: 50px;
opacity: 0;
background-color: #fff;
color: #555;
text-shadow: none;
border-radius: 25px;
padding: 5px 10px;
box-shadow: 2px 2px 3px #0005;
}
.flair > .se-tooltip.active {
bottom: 10px;
opacity: 1;
}
/* Flair Wheel Styles */
.flair.wheel {
width: 200px;
height: 250px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-shadow: 1px 1px 2px #0005;
}
.flair.wheel .user-image {
width: 100px;
height: 100px;
border-radius: 50%;
box-shadow: 2px 2px 3px #0005;
}
.flair.wheel .username {
font-size: 30px;
margin: 0;
}
.flair.wheel .badges > li > span { position: relative; }
.flair.wheel .badges > li:first-of-type > i { color: #5c9; }
.flair.wheel .badges > li:not(:first-of-type) > span::before {
content: '';
position: absolute;
top: 50%;
left: -15px;
transform: translateY(-40%);
width: 10px;
height: 10px;
border-radius: 50%;
}
.flair.wheel .badges > li:nth-child(2) > span::before { background-color: #fb3; }
.flair.wheel .badges > li:nth-child(3) > span::before { background-color: #aaa; }
.flair.wheel .badges > li:nth-child(4) > span::before { background-color: #c95; }
.flair.wheel .sites {
position: absolute;
top: 10px;
left: 0;
width: 100%;
height: 55%;
}
.flair.wheel .sites > li { position: absolute; }
.flair.wheel .sites > li > a > img {
width: 35px;
height: 35px;
background-color: #fffa;
border-radius: 50%;
padding: 2px;
box-shadow: 2px 2px 3px #0005;
cursor: pointer;
transition: 0.3s cubic-bezier(0.5, -2.5, 1.0, 1.2) all;
z-index: 1;
}
.flair.wheel .sites > li > a:hover > img {
width: 40px;
height: 40px;
background-color: #fff;
}
.flair.wheel .sites > li:nth-child(1) {
top: -15px;
left: 50%;
transform: translate(-50%);
}
.flair.wheel .sites > li:nth-child(2) {
top: 0px;
left: 15%;
transform: translate(-20%);
}
.flair.wheel .sites > li:nth-child(3) {
top: 0px;
left: 70%;
transform: translate(-20%);
}
.flair.wheel .sites > li:nth-child(4) {
top: 45%;
left: 80%;
transform: translate(-20%, -50%);
}
.flair.wheel .sites > li:nth-child(5) {
top: 45%;
left: -5px;
transform: translateY(-50%);
}
.flair.wheel .sites > li:nth-child(6) {
top: 79%;
left: 3px;
transform: translateY(-50%);
}
.flair.wheel .sites > li:nth-child(7) {
top: 79%;
right: 3px;
transform: translateY(-50%);
}
/* To Organize in a Row instead of Column */
.user-flair-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
/* Global Styles */
ul {
padding: 0;
listy-style-type: none;
}
ul > li {
display: inline-block;
padding: 0 10px;
}
/* Template Overrides */
html, body {
margin: 0;
height: 100%;
background-color: #333 !important;
background-image: linear-gradient(45deg, #333, #555) !important;
background-image: -webkit-linear-gradient(45deg, #333, #555) !important;
}
.primary-content {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.primary-content > .lead { font-size: 25px; }
<link href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2940219/PerpetualJ.css" rel="stylesheet"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" rel="stylesheet"/>
<div id="primary-content" class="primary-content">
<div class="user-flair-container">
<div id="user-flair-wheel"></div>
</div>
</div>
Best of luck to all of you in your future endeavors!