Space
article thumbnail
반응형

프로젝트명

투두리스트 (ToDoList)


기간 / 인원

23.11.26 ~ 23.11.30 (5일) / 1인


소개

 

Vanilla JS(바닐라 자바스크립트)를 사용한 투두리스트(ToDoList) 사이트입니다.


배포 링크

🔗 ToDoList (Vercel로 배포하였습니다.)

 

크롬 앱 만들기

 

to-do-list-red-nu.vercel.app


Github 링크

https://github.com/ghvhdh333/ToDoList

 

GitHub - ghvhdh333/ToDoList: 바닐라 JS로 ToDoList 만들기

바닐라 JS로 ToDoList 만들기. Contribute to ghvhdh333/ToDoList development by creating an account on GitHub.

github.com


기술 스택

[ FE ]

Vanilla JS, HTML, CSS

 

[ Environment ]

VScode, Git, Github, Vercel


회고

로그인 기능 구현

// login.js
const loginForm = document.querySelector("#login-form");
const loginInput = document.querySelector("#login-form input");
const greeting = document.querySelector("#greeting");

const HIDDEN_CLASSNAME = "hidden";
const USERNAME_KEY = "username"

function onLoginSubmit(event) {
    event.preventDefault();
    loginForm.classList.add(HIDDEN_CLASSNAME);
    const username = loginInput.value;
    localStorage.setItem(USERNAME_KEY, username);
    paintGreetings(username);
}

function paintGreetings(username){
    greeting.innerText = `Hello! ${username} :)`;
    greeting.classList.remove(HIDDEN_CLASSNAME);
}

const savedUsername = localStorage.getItem(USERNAME_KEY);

if(savedUsername === null){
    // 유저 이름을 입력하지 않은 상태이면
    loginForm.classList.remove(HIDDEN_CLASSNAME);
    loginForm.addEventListener("submit", onLoginSubmit)
} else {
    // 유저 이름을 입력된 상태이면
    paintGreetings(savedUsername);

}

- input에 값을 입력하고 로그인 버튼을 눌리면 button의 submit 이벤트를 실행되어 브라우저가 자체적으로 새로고침을 하기 때문에 로그인이 되지 않으므로, 브라우저 자체적으로 새로고침 하는 것을 방지하기 위해 preventDefault()를 사용하여 새로고침을 방지합니다.

- 로그인을 하고 유저가 인위적으로 새로고침을 하면, 그 또한 로그인이 풀리기 때문에 유저의 이름을 기억하기 위해 localStorage를 통해 유저의 정보를 기억합니다.


LocalStorage를 이용한 ToDoList 상태관리

- toDoForm의 submit 이벤트로 인해 페이지가 새로고침 되는 것을 방지하기 위하여 preventDefault를 사용하였습니다.

- input 값을 submit 하면 값을 newTodo에 할당하고, input box의 값을 빈문자열("")로 초기화하였습니다.

- newToDoObj의 객체에 id 값을 Date.now()를 사용하여 밀리 초까지 나오는 숫자를 활용하였습니다.

- 로컬스토리지에 데이터를 저장할 때 JSON.stringify로 객체를 JSON 포맷의 문자열로 변환시키고 저장하였습니다.

- 데이터를 화면에 보여줄 때 저장된 로컬스토리지의 데이터를 JSON.parse를 하여 다시 객체로 변환시켜 화면에 나타냈습니다.

- check box의 체크유무에 따라 checked 클래스를 추가, 제거하여, 유저가 알 수 있게 CSS 효과를 넣었습니다.

 

// todo.js
const toDoForm = document.querySelector("#todo-form");
const toDoInput = document.querySelector("#todo-form input");
const toDoList = document.querySelector("#todo-list");

// todolist 저장할 배열
let toDos = [];

// JSON.stringify를 통해 객체를 JSON 포맷의 문자열로 변환시키고, localStorage에 저장한다.
function saveToDos () {
    localStorage.setItem("todos", JSON.stringify(toDos));
}

// filter를 사용하여 'x'버튼을 누르면 해당 버튼의 상위 <li> 태그를 제외한 다른 todolist를 추출하여 
// toDos 배열에 재할당하고, 재할당 된 것을 토대로 다시 JSON 포맷의 문자열로 변환시키고, 로컬스토리지에 저장한다.
function deleteToDo (event) {
    const li = event.target.parentElement;
    li.remove();
    toDos = toDos.filter(el => el.id !== Number(li.id));
    saveToDos();
}

// handleToDoSubmit를 통해 받은 newToDoObj 객체를 각 분야 알맞게 값을 할당하고,
// 체크유무에 따라 checked 클래스를 추가, 제거하여, 체크효과를 css로 넣어주며, 화면에 나타낸다.
function paintToDo(newToDoObj){
    const list = document.createElement("li");
    list.id = newToDoObj.id;
    
    const checkbox = document.createElement("input")
    checkbox.type = 'checkbox';
    checkbox.classList = 'checkBox';
    
    checkbox.addEventListener('click', handleCheckbox);

    const span = document.createElement("span");
    span.classList = 'toDoSpan';
    span.innerText = newToDoObj.text;

    // 기존의 체크박스의 체크 유무에 대해 보여주는 화면 분기
    if(newToDoObj.checked) {
        span.classList.add('checked');
        checkbox.checked = true;
    } else {
        span.classList.remove('checked');
        checkbox.checked = false;
    }

    function handleCheckbox(event){
        const listChecked = event.target.checked;
        if(listChecked){
            span.classList.add('checked');
            newToDoObj.checked = listChecked;
            saveToDos();
        } else {
            span.classList.remove('checked');
            newToDoObj.checked = listChecked;
            saveToDos();
        }
    }

    const div = document.createElement("div");

    const button = document.createElement("button");
    button.innerText = "❌";

    button.addEventListener("click", deleteToDo);

    div.appendChild(checkbox);
    div.appendChild(span);
    list.appendChild(div);
    list.appendChild(button);

    toDoList.appendChild(list);
}

// input의 submit시 새로고침 되는 것을 방지하기 위해 preventDefault()를 사용하고,
// newTodo에 사용자로부터 입력받은 값을 할당하고, 초기화 시킨다.
// 해당 input 값과, id, 체크유무를 newToDoObj에 할당하고 toDos 배열에 push하고, 
// JSON.stringify를 통해 객체를 JSON 포맷의 문자열로 변환시키고, localStorage에 저장한다.
function handleToDoSubmit(event) {
    event.preventDefault();
    const newTodo = toDoInput.value;
    toDoInput.value = "";
    const newToDoObj = {
        id : Date.now(),
        text : newTodo,
        checked : false,
    }
    toDos.push(newToDoObj);
    paintToDo(newToDoObj);
    saveToDos();
}

// toDoForm의 input에 값을 입력하고 엔터를 누르면 
// submit 이벤트와, handleToDoSubmit 함수가 실행되도록 한다.
toDoForm.addEventListener("submit", handleToDoSubmit);

// saveToDos 함수를 통해 저장된 (객체를 JSON 포맷의 문자열로 변환된) 값을 불러온다.
const savedToDos = localStorage.getItem("todos");

// savedToDos를 통해 불러온 값이 있으면
// JSON.parse를 통해 JSON 포맷의 문자열을 객체로 변환하고, 
// 모든 요소들을 paintToDo 함수를 통해 화면에 나타낸다.
if(savedToDos) {
    const parsedToDos = JSON.parse(savedToDos);
    toDos = parsedToDos;
    parsedToDos.forEach(el => paintToDo(el))
}

 


랜덤 배경화면 & 명언 구현

- Math.floor(Math.random() * arr.length) 를 사용하여 랜덤 한 숫자를 구하고,

그 숫자를 사용하여 배경화면과, 명언을 무작위로 나타나게 하였습니다.

// background.js
const images = [
    "0.jpg",
    "1.jpg",
    "2.jpg",
    "3.jpg",
    "4.jpg",
    "5.jpg",
]

// 0 ~ images.length 까지 랜덤한 숫자를 발생시킨다.
const chosenImage = images[Math.floor(Math.random() * images.length)];

const bgImage = document.createElement("img");
bgImage.src = `img/${chosenImage}`;

document.body.appendChild(bgImage);
// quotes.js
const quotes = [
    {
        quote : "Don't dwell on the past.",
    },
    {
        quote : "Believe in yourself.",
    },
    {
        quote : "Follow your heart.",
    }, ... 
];

const quoteId = document.querySelector("#quote span:first-child");

// 0 ~ quotes.length 만큼 랜덤한 숫자를 반환해 줌
const todaysQuote = quotes[Math.floor(Math.random() * quotes.length)];

quoteId.innerText = todaysQuote.quote;

현재 날짜, 시간 기능 구현

- new Date()를 사용하여 현재 날짜와 시간을 구하였고,

padStart()를 이용하여 1자리 숫자일 때 '1'초가 아닌 '01'초가 되도록 구현하였습니다.

// clock.js
const clock = document.querySelector('#clock');
const date = document.querySelector('#date');

function getClock (){
    const date = new Date();
    const hours = String(date.getHours()).padStart(2, "0");
    const minutes = String(date.getMinutes()).padStart(2, "0");
    const seconds = String(date.getSeconds()).padStart(2, "0");
    clock.innerText = `${hours}:${minutes}:${seconds}`;
}

function getDate() {
    const getdate = new Date().toLocaleDateString();
    const slicedDate = getdate.slice(0, getdate.length -1);
    date.innerText = slicedDate;
}

getClock();
getDate();
setInterval(getClock, 1000);

사용자의 현재 위치를 기반으로 한 도시이름, 날씨를 알려줌

- navigator.geolocation.getCurrentPosition()을 사용하여 현재 위치에 대한 위도, 경도 등을 구하였고,

윗 방법으로 구한 위도, 경도를 날씨 API (openweathermap.org)를 통해 사용자가 현재 위치하고 있는 도시와 날씨 등을 알려줍니다.

(API KEY는 공개하지 않으므로, ~~~ 로 대체하였습니다.)

// weather.js
const API_KEY = '~~~';

function onGeoOk(position) {
    const lat = position.coords.latitude;
    const lon = position.coords.longitude;

    const URL = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric`;

    fetch(URL)
        .then(res => res.json())
        .then(data => {
            const city = document.querySelector("#weather span:nth-child(1)");
            const weather = document.querySelector("#weather span:nth-child(2)");
            const temp = document.querySelector("#weather span:nth-child(3)");
            const wind = document.querySelector("#weather span:nth-child(4)");

            city.innerText = data.name;
            weather.innerText = `/ ${data.weather[0].main}`;
            temp.innerText = `/ ${data.main.temp} ℃`;
            wind.innerText = `/ ${data.wind.speed} m/s`;
        });
}

function onGeoError(){
    alert("Can't find you. No weather for you.");
}

navigator.geolocation.getCurrentPosition(onGeoOk, onGeoError);

부족한 점

- CSS 애니메이션 숙련도 부족 : ToDo의 List를 더욱 예쁘게 움직이고 싶었으나, 마음대로 되지 않았다.

반응형
profile

Space

@Space_zero

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!