FrontEnd/JavaScript

nomadcoders_바닐라JS로 크롬앱 만들기_day5

Leo.K 2023. 3. 17. 16:29

2023-03-17
#7 TO-DO-LIST
우리는 추가와 삭제가 가능한 멋진 todo-list를 위해 기본적인 설정을 화면에 구현했다. 이 내용은 이전 게시글에서 이미 기록한 개념이기 때문에 생략하고 마지막에 전체 소스코드를 보여주려고 한다. 

하지만 새로고침을 하거나 이용자가 누구인가와 관계없이 똑같은 todo-list가 나온다면 우리는 todo-list를 그 때마다 계속해서 작성해야할 것이다. 만약 todo-list를 1,000개 작성했는데 단숨에 날라간다면...? 그건 어딘가 부족한 todo-list 일 거다.
그래서 우린 todo-list에 나타낸 텍스트를 저장하는 기능과 로딩하는 기능, 이미 달성한 일은 제거하도록 삭제하는 기능을 추가로 구현해야 한다. 


1. todo - list의 배열 생성
    1) const toDos = [ ]; // toDo에 들어오는 텍스트를 배열로 묶어 보관하기 위해 빈 array를 생성해준다.

2. 저장 기능을 함수를 정의한다.
    1) 아직 기능을 하진 않지만 우리는 화면에 나타낸 텍스트를 저장할 것이기 때문에 대충 그러한 기능을 하는 함수가 있다 고 치고 빈 함수
function saveToDos( ) {
};
를 생성한다.
    2) 앞에서 만들었던 함수 handleToDoSubmut( ); 의 맨 마지막에 저장 기능을 실행할 saveToDos(); 넣어두고 다음에서
기능을 구현한다.


3. todo - list를 저장하는 기능을 수행하는 함수 설정
    1) function saveToDos( ) { localStorage.setItem("todos",toDos); } 에 "todos"라는 이름의 카테고리로 저장한다. 
    2) 하지만 이렇게 저장하게 되면 직접 localStorage 에서 확인해봤을 때 값들이 array안에서 String의 형태가 아닌 상태로 저장된다.
예) key: todos value: a,b,c
    3) 하지만 우리는 localStorage에 저장한 값들을  로딩하기 위해서는 배열 형태의 String이 저장 되었으면 하는데, 이를 위해 JSON.stringify() 라는 객체를 사용한다. 이 도구는 우리가 대입한 값을 알아서 string의 형태로 바꿔줄 것이다.
예) key: todos value: ["a", "b", "c"]
배열 혹은 객체를 문자열의 형태로 저장(JSON.stringify()) -> 추후에 꺼내어 사용할 때는 JS가 이해할 수 있는 형태의 배열 혹은 객체로 변환(JSON.parse)

그렇다고 해도 아직 모든 문제점이 해결된 것은 아니다. 새로고침 후에 특정 값을 추가하면, 기존에 저장되어 있던 값이 사라지기 때문이다. 이는 브라우저가 새로고침 되어도 로컬 스토리지의 내용은 삭제되지 않지만, 중요한 점은 로컬스토리지에 저장하는 toDos 배열이 초기화 된다는 점이다. 따라서 새로고침 후 값을 새롭게 저장하면 초기화 된 toDos 배열이 저장소에 저장되기 때문에 이전 값이 보존되지 않는 경우가 발생한다. 이는 다음 강의에서 해결해보도록 하자.

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

const toDos = [];

//저장하기
function saveToDos(){
    localStorage.setItem("toDos", JSON.stringify(toDos));
}

//삭제하기
function deleteToDo(event){
    const deleteLi = event.target.parentElement; //target -> 클릭된 html의 element, parentElement -> 클릭된 태그의 부모 태그
    deleteLi.remove();
}

//추가하기
function paintToDo(newToDo){
    const li = document.createElement("li");
    const span = document.createElement("span");
    span.innerText = newToDo;

    const button = document.createElement("button");
    button.innerText = "🚫";
    button.addEventListener("click", deleteToDo);

    li.appendChild(span);
    li.appendChild(button);

    toDoList.appendChild(li);
}

//버튼 이벤트
function handleToDoSubmit(event){
    event.preventDefault();
    const newToDo = toDoInput.value; //input의 현재 value를 변수에 복사하는 것
    toDoInput.value = "";

    toDos.push(newToDo); // 그리기 전에 localStorage 에 저장을 먼저
    paintToDo(newToDo);
    saveToDos();
}

toDoForm.addEventListener("submit", handleToDoSubmit);

 

#7.4-5 Loading To Do
위에서 설명한 대로 todolist를 등록하여 localstorage에 저장한 뒤에 새로고침을 하게 되면 리스트는 저장이 된다. 
하지만 이 상태에서 새로운 리스트를 추가하게 되면 기존의 localstorage의 내용은 무시하고 새로운 값이 덮어씌워진다. 
이는 위에서 설명한대로 브라우저가 종료되기 전까지 localstorage의 값은 유지되지만, toDos 배열은 초기화 되기 때문이라고 했다.
따라서 브라우저가 새로고침 되어도 값이 초기화 되지 않도록 toDos 배열을 빈 배열이 아닌 localstorage에 저장된 값을 불러와서 저장해주는 로딩 작업이 필요하다.

const savedToDos = localStorage.getItem(TODOS_KEY);
if (savedToDos != null){
    const parsedToDos = JSON.parse(savedToDos);
    // parsedToDos.forEach(sayHello); 배열의 각 요소에 대해 함수를 실행할 수 있다.
    // parsedToDos.forEach((item) =>  console.log("this is the turn of ", item)); //shorcut
    toDos = parsedToDos;
    parsedToDos.forEach(paintToDo);

위의 코드는 자바스크립트의 코드 중 가장 마지막에 추가하도록 한다. 이벤트 리스너와 같은 내용은 다 추가가 된 상태에서 localstorage에 저장된 값이 있는지 여부를 확인하는 것이기 때문이다. 
null이 아니라면 즉 값이 존재한다면, 해당 값을 불러와서 toDos 배열에 초기화해주고, 리스트를 새롭게 그리는 것이다. 

이때 forEach문을 사용하면 된다. forEach문을 사용하면 parsedToDos 배열에 존재하는 각 element 마다  paintToDo함수를 실행할 수 있다. 
다른 방법으로는 shortcut 방식으로 짧게 표현이 가능한데, 자바의 람다 혹은 익명 메서드와 비슷하게 생각하면 될 것이다.

#7.6-8 Deleting To Do
다음 문제는 특정 todolist를 삭제한 경우 태그만 삭제하는 것이 아니라 새로고침 후에도 삭제 여부가 적용되게 하기 위해서는 localstorage에서도 삭제를 해주어야 한다. 
하지만 배열의 특성상 중간에 있는 데이터를 삭제하는 것이 어렵고, 특정 데이터를 찾도록 해당 데이터의 인덱스를 찾는 것 또한 쉬운 일이 아니다. 따라서 이전처럼 todolist의 텍스트만 저장하는 것이 아니라 해당 text를 고유하게 식별할 수 있도록 식별값을 함께 Object형식으로 저장해줄 것이다.  

function handleToDoSubmit(event){
    event.preventDefault();
    const newToDo = toDoInput.value; //input의 현재 value를 변수에 복사하는 것
    toDoInput.value = "";

    const newToDoObj = {
        text : newToDo,
        id : Date.now()
    };
    toDos.push(newToDoObj); // 그리기 전에 localStorage 에 저장을 먼저
    paintToDo(newToDoObj);
    saveToDos();
}

 

마지막으로 Filter함수에 대해서 알아보자. 
위에서 잠깐 보았던 forEach와 같은 로직으로 실행은 된다. 배열의 각 요소마다 한 번씩은 함수를 호출하는 것이 아니다. 
차이점은 forEach가 명령실행이라면 filter는 유효성 검증이 더 어울리는 표현일 것이다. 
filter()에 파라미터로 전달되는 함수는 반드시 boolean값을 전달해야 하는데, true인 경우는 유지, false인 경우는 제외하여 새로운 배열을 만들어서 반환한다.
우리는 이렇게 만들어진 새로운 배열을 localStorage에 덮어씌워 주면서 삭제하는 기능을 구현할 것이다. 

function deleteToDo(event){
    const deleteLi = event.target.parentElement; //target -> 클릭된 html의 element, parentElement -> 클릭된 태그의 부모 태그
    deleteLi.remove();
    toDos = toDos.filter((todo) => todo.id !== parseInt(deleteLi.id));
    saveToDos();
}

 

#8 WEATHER API
https://openweathermap.org/

 

Сurrent weather and forecast - OpenWeatherMap

Access current weather data for any location on Earth including over 200,000 cities! The data is frequently updated based on the global and local weather models, satellites, radars and a vast network of weather stations. how to obtain APIs (subscriptions w

openweathermap.org

위의 사이트에 접속하여 회원가입을 진행한 후에 Weather API의 Current Weather Data API DOC를 참조하도록 하자.
참고로 API KEY는 마이페이지에서 무료로 발급받을 수 있는데, 가입할 때 기입한 이메일 인증을 진행한 후 30분 정도 후에 활성화 되기 떄문에 url호출시 에러가 발생한다고 당황하지 않아도 된다.

보통 온도가 화씨온도로 표기가 되는데 우리가 편한 섭씨 온도로 바꿔주기 위해 호출 URL에 units=metric을 추가하자.

const API_KEY = "{YOUR API}";

function onGeoSuccess(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`;
    // console.log(url);

    fetch(url)
        .then((response) => response.json())
        .then((data) => {
            const weather = document.querySelector("#weather span:first-child");
            const city = document.querySelector("#weather span:last-child");
            city.innerText = data.name;
            weather.innerText = `${data.weather[0].main} / ${data.main.temp}`;
        });
    //url에 접속할 필요없이 자바스크립트가 접속해서 데이터를 가져와 준다.
    //단, 서버의 던진 request에 대한 response의
}

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

navigator.geolocation.getCurrentPosition(onGeoSuccess, onGeoError);