Front-End/JavaScript

[노마드코더 / 바닐라JS] momentum 클론 코딩(4)_To do and Weather

Olivia Kim 2023. 3. 25. 12:26
반응형

 

📚 노마드코더의 '바닐라 JS로 크롬 앱 만들기'를 수강하며 배우거나 추가적으로 찾아본 것들을 정리한 내용입니다.

 

To do

Set up

input에 to do를 입력하고 버튼 또는 엔터를 눌렀을 때 해당 to do 데이터를 변수에 보관하고, 사용자가 input에 입력한 값은 지워 화면에서 보이지 않도록 설정한다.

// HTML

<body>
  <form class="hidden" id="login-form">
    <input type="text" maxlength="15" placeholder="Whai is your name?" required />
    <button>Log In</button>
  </form>
  <h2 id="clock">00:00:00</h2>
  <h1 class="hidden" id="greeting"></h1>
  <form id="todo-form">
    <input type="text" placeholder="Write a To Do and Press Enter" required />
  </form>
  <ui id="todo-list"></ui>
  <div id="quote">
    <span></span>
    <span></span>
  </div>
  <script src="./js/greetings.js"></script>
  <script src="./js/clock.js"></script>
  <script src="./js/quotes.js"></script>
  <script src="./js/background.js"></script>
  <script src="./js/todo.js"></script>
</body>
// JavaScript

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

function handleToDoSubmit(event) {
  // submit 이벤트의 새로고침을 막는다.
  event.preventDefault();
  // newTodo 변수에 현재 입력한 value를 넣는다.
  const newTodo = toDoInput.value;
  // 화면에 보이는 value를 초기화시킨다.
  toDoInput.value = '';
}

toDoForm.addEventListener('submit', handleToDoSubmit);

 

 

Adding To Dos

사용자가 입력한 to do 데이터를 화면에 그리는 함수를 만든다.

// JavaScript

// to do를 그리는 역할을 담당한다.
function paintToDo(newTodo) {
  // li, span html 요소를 만든다.
  const li = document.createElement('li');
  const span = document.createElement('span');
  // li 안에 span 요소를 넣는다.
  li.appendChild(span);
  // span에는 newTodo 값을 넣는다.
  span.innerText = newTodo;
  // html에 작성했던 toDoList(ul) 안에 li를 넣는다.
  toDoList.appendChild(li);
}

 

 

Deleting To Dos(1)

사용자가 to do를 완료했을 경우 버튼을 클릭해 해당 to do를 지울 수 있도록 하고, 화면에서 해당 요소를 지운다.

// JavaScript

// to do를 지운다.
function deleteToDo(event) {
  // 어떤 li를 지워야하는지 찾아내기 위해 click된 button의 부모 요소(li)를 찾는다.
  const li = event.target.parentElement;
  // 해당 요소를 지운다.
  li.remove();
}

// to do 요소를 만들어 html에 그린다.
function paintToDo(newTodo) {
  const li = document.createElement('li');
  const span = document.createElement('span');
  span.innerText = newTodo;
  // 작성한 투 두 리스트를 삭제할 수 있는 버튼 요소를 만든다.
  const button = document.createElement('button');
  // button 안에 X (삭제) 이모지를 넣는다.
  button.innerText = '❌';
  button.addEventListener('click', deleteToDo);
  // append는 맨 마지막에 들어가야 한다! (값을 가진 상태로)
  li.appendChild(span);
  li.appendChild(button);
  toDoList.appendChild(li);
}

 

 

Saving To Dos

사용자가 입력한 to do를 일회성이 아닌 끝마칠 때까지 두고두고 저장할 수 있도록, 브라우저의 'local storage'에 값을 저장한다. local storage는 페이지를 새로 고침하고 브라우저/OS를 다시 실행해도 데이터가 사라지지 않고 남아있다. 이때, local storage의 키와 값은 반드시 문자열이어야 한다.

// JavaScript

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

// 저장할 to Dos의 배열
const toDos = [];
console.log(toDos)

function saveToDos() {
  localStorage.setItem('todos', toDos);
}


// to do를 지운다.
function deleteToDo(event) {
  const li = event.target.parentElement;
  li.remove();
}

// to do 요소를 만들어 html에 그린다.
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);
}

// to do 데이터를 받는다.
function handleToDoSubmit(event) {
  event.preventDefault();
  const newTodo = toDoInput.value;
  toDoInput.value = '';
  // to Dos 배열에 newTodo 값을 push한다.
  toDos.push(newTodo);
  paintToDo(newTodo);
  saveToDos();
}


toDoForm.addEventListener('submit', handleToDoSubmit);

 

 

여기까지 구현을 진행했을 때 발생하는 local storage 저장 관련 이슈

 

local storage는 문자열(text)만 저장 가능하다. 나중에 쉽게 데이터를 넣고 빼기 위해서는 ['로컬스토리지에', '있는', '데이터'] 처럼 배열 형태로 넣어야 인덱스로 쉽게 접근이 가능할텐데 어떻게 넣을 수 있을까?

 

 

바로, 브라우저가 가지고 있는 기능을 이용한다. JSON.stringify()! 해당 메서드는 JavaScript의 값이나 객체를 JSON 문자열로 변환해 준다. (그렇다면 JSON은 뭔데? JSON은 JavaScript 객체, 배열 등의 데이터를 표현하는 텍스트 기반의 방식이다.) 따라서 JSON.stringify()를 이용하면 객체도, 배열도 JSON 형식의 string으로 바꿔주므로 텍스트값만 넣을 수 있는 local storage에도 담아낼 수 있다.

 

 

// 기존 코드
function saveToDos() {
  localStorage.setItem('todos', toDos);
}

// JSON.stringify() 이용
function saveToDos() {
  localStorage.setItem('todos', JSON.stringify(toDos));
}

 

 

 

JSON.stringify()의 힘을 빌려 local storage에 데이터를 set하면 와 같이 형식이 변경되어 저장되는 것을 확인할 수 있다.

 

 

저장된 값을 보고 생겨난 질문

❓ 그렇다고 해서 text만 저장되는 local storage의 value값이 어떻게 0, 1, 2 인덱스 순서까지 보이는 거지?

❗ local storage의 키와 값은 반드시 문자열이어야 하나, JSON을 사용하면 객체를 쓸 수 있긴 하다고 한다. 아하!

 

 

 

Loading To Dos

local storage에 사용자가 입력한 값을 저장하는 것까지는 마쳤다. 그러나 해당 내역을 화면에 띄워주고 있지는 않으므로, 해당 기능을 구현한다. local storage에 저장한 값을 가져오려면, JSON.parse를 이용해 JSON으로 저장해 뒀던 내역을 가지고 온다.

 

 

 

// JavaScript

const TODOS_KEY = 'todos';

...

// local storage에 있는 값을 가져와 화면에 그린다.
const savedToDos = localStorage.getItem(TODOS_KEY);

if(savedToDos !== null) {
  const parsedToDos = JSON.parse(savedToDos);
  // item을 인자로 넘겨주지 않아도, forEach가 자동적으로 각 요소를 하나씩 실행하며 넘긴다.
  // 기존에 만들어뒀던 to do 요소를 화면에 그려주는 함수인 paintToDo를 호출한다.
  parsedToDos.forEach(paintToDo);
}

 

 

Deleting To Dos(2)

지금까지의 코드로는 사용자가 입력한 to do인 li를 삭제할 때 화면에서는 지워지지만 local storage에 있는 값은 지워지지 않는다. 이때, local storage에 있는 값을 삭제하려면 '어떤 값'을 삭제하려는 건지 정확하게 알아야 하는데 현재 기준으로는 값이나 순번 등을 알 수 없다.

 

 

따라서 local storage에 저장할 때

 

  1. 일반 배열 형태가 아닌 '객체' 형태로 데이터를 저장하도록 수정한다.
  2. 데이터를 저장할 때 중복되지 않는 id값을 각각 부여해 저장한다.

 

id값을 저장할 때는 Date.now()를 사용한다. Date.now()는 ms(1/1000)초를 반환해 주는 함수이다.

 

 

// JavaScript

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

const TODOS_KEY = 'todos';

// 저장할 to Dos의 배열
// 배열 값이 변경되어야 하므로 기존 const -> let으로 변경
let toDos = [];

// local storage에 입력한 to do를 저장한다.
function saveToDos() {
  localStorage.setItem(TODOS_KEY, JSON.stringify(toDos));
}

// to do를 지운다.
function deleteToDo(event) {
  const li = event.target.parentElement;
  li.remove();
  // 삭제 버튼을 클릭한 외의 li는 남겨두고 다시 새 배열로 반환한다.
  toDos = toDos.filter(toDo => toDo.id !== Number(li.id));
  // local storage에도 해당 내용을 반영할 수 있도록 saveToDos() 함수를 호출한다.
  saveToDos();
}

// to do 요소를 만들어 html에 그린다.
function paintToDo(newTodo) {
  const li = document.createElement('li');
  // date.now()로 생성한 값을 li요소의 id값으로 부여한다.
  li.id = newTodo.id;
  const span = document.createElement('span');
  // span에는 text값만 출력한다.
  span.innerText = newTodo.text;
  const button = document.createElement('button');
  button.innerText = '❌';
  button.addEventListener('click', deleteToDo);
  li.appendChild(span);
  li.appendChild(button);
  toDoList.appendChild(li);
}

// to do 데이터를 받는다.
function handleToDoSubmit(event) {
  event.preventDefault();
  const newTodo = toDoInput.value;
  toDoInput.value = '';
  // 기존과 같이 text만 저장하지 않고, id값도 같이 저장한다.
  const newTodoObj = {
    text: newTodo,
    id: Date.now(),
  }
  toDos.push(newTodoObj);
  paintToDo(newTodoObj);
  saveToDos();
}

toDoForm.addEventListener('submit', handleToDoSubmit);

// local storage에 있는 값을 가져와 화면에 그린다.
const savedToDos = localStorage.getItem(TODOS_KEY);

if(savedToDos !== null) {
  const parsedToDos = JSON.parse(savedToDos);
  // item을 인자로 넘겨주지 않아도, forEach가 자동적으로 각 요소를 하나씩 실행하며 넘긴다.
  parsedToDos.forEach(paintToDo);
}

 

 


Weather

JavaScript에서 사용자의 현재 위치 정보를 가져올 수 있도록 하는 geolocation API와 날씨 API를 조합해 사용자가 있는 위치의 날씨를 보여준다.

 

 

[참고] weather API

 

Weather API - OpenWeatherMap

Please, sign up to use our fast and easy-to-work weather APIs. As a start to use OpenWeather products, we recommend our One Call API 3.0. For more functionality, please consider our products, which are included in professional collections.

openweathermap.org

 

 

// HTML

<body>
  <form class="hidden" id="login-form">
    <input type="text" maxlength="15" placeholder="Whai is your name?" required />
    <button>Log In</button>
  </form>
  <h2 id="clock">00:00:00</h2>
  <h1 class="hidden" id="greeting"></h1>
  <form id="todo-form">
    <input type="text" placeholder="Write a To Do and Press Enter" required />
  </form>
  <ui id="todo-list"></ui>
  <div id="quote">
    <span></span>
    <span></span>
  </div>
  <div id="weather">
    <span></span>
    <span></span>
  </div>
  <script src="./js/greetings.js"></script>
  <script src="./js/clock.js"></script>
  <script src="./js/quotes.js"></script>
  <script src="./js/background.js"></script>
  <script src="./js/todo.js"></script>
  <script src="./js/weather.js"></script>
</body>
// JavaScript

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=weatherAPI에서 발급받은 본인의 고유 API값&units=metric`;
  // fetch는 promise이다
  // promise는 당장 뭔가 일어나지 않고 시간이 좀 걸린 뒤에 일어난다.
  fetch(url).then(response => response.json()).then(data => {
    const location = document.querySelector('#weather span:first-child');
    const weather = document.querySelector('#weather span:last-child');
    location.innerText = data.name;
    weather.innerText = `${data.weather[0].main} / ${data.main.temp}℃`;
  });
}

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

navigator.geolocation.getCurrentPosition(onGeoSuccess, onGeoFail);

 

 

⚠️ API KEY는 사용자마다 각각 다르게 부여되는 고유한 값이므로, 타인에게 노출되면 예상치 못한 보안 이슈나 해킹으로 인한 금액 청구 관련 문제가 발생할 수 있다.

 

 

나는 코드를 github에도 push하고 있으므로 API 키를 노출하지 않고 코드를 작성해야 했다. 대부분 .env라는 환경변수 파일을 따로 만들어 해당 파일에 저장하고 github에는 올라가지 않도록 하고 있지만, 나는 html, css, js로만 구성된 간단한 파일이었기에 따로 .env를 만들기엔 부담이 있었다. 그러던 중 기술 매니저님께서 같은 고민을 하던 분의 블로그를 찾아주셔서 해당 내용을 참고해 아래와 같이 해결했다. 🙇

 

 

1. apikey.js 파일을 만들고 해당 파일 내에 부여받은 key값을 넣는다. (파일 명은 자유)

const config = {
	'apikey': '부여받은API키값입력'
};

 

 

2. index.html 내에 apikey.js 파일을 연결한다.

// HTML

<body>

...

  <script src="/apikey.js"></script>
  <script src="./js/greetings.js"></script>
  <script src="./js/clock.js"></script>
  <script src="./js/quotes.js"></script>
  <script src="./js/background.js"></script>
  <script src="./js/todo.js"></script>
  <script src="./js/weather.js"></script>
</body>

 

우리가 해당 KEY를 불러올 곳은 weather.js이고, KEY값이 들어있는 곳은 apikey.js이다. 즉 apikey.js 내부에 있는 값을 → wheather.js가 내려받아 사용해야 하므로 apikey.js를 연결하는 위치가 weather.js보다 위쪽에 위치해야 한다! 그래야 apikey.js 파일을 먼저 읽고 그 데이터를 바탕으로 wheather.js 파일을 읽기 때문이다.

 

 

3. whater.js에서 해당 api key를 상수로 할당하여 사용한다.

const WEATHER_API = config.apikey;

 

 

4. gitignore 파일을 생성해 apikey.js를 추가하여 github에 해당 파일이 업로드되지 않도록 한다.

 

 


 

✅ 4일간의 바닐라 JS로 크롬 앱 만들기 강의 수강이 끝났다! 강의보다 실습을 훨씬 선호하는 편이라 강의 듣는 걸 그다지 좋아하지 않는데, 실습하면서 배우는 클론 코딩이기도 하고 목소리도 흡입력 있으셔서 열심히 들었다. 강의를 완강했으니 이제 코드를 최대한 보지 않고 직접 구현해 보는 것으로 마무리를 하려 한다. 마지막까지 파이팅💪

 


[관련 글 함께 보기]

 

 


[참고 자료]

https://nomadcoders.co/javascript-for-beginners

 

바닐라 JS로 크롬 앱 만들기 – 노마드 코더 Nomad Coders

Javascript for Beginners

nomadcoders.co

https://ko.javascript.info/localstorage#ref-539

 

localStorage와 sessionStorage

 

ko.javascript.info

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

 

JSON.stringify() - JavaScript | MDN

JSON.stringify() 메서드는 JavaScript 값이나 객체를 JSON 문자열로 변환합니다. 선택적으로, replacer를 함수로 전달할 경우 변환 전 값을 변형할 수 있고, 배열로 전달할 경우 지정한 속성만 결과에 포함

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/API/Navigator/geolocation

 

Navigator.geolocation - Web API | MDN

Navigator.geolocation 읽기 전용 속성은 웹에서 장치의 위치를 알아낼 때 사용할 수 있는 Geolocation 객체를 반환합니다. 웹 사이트나 웹 앱은 위치정보를 사용해 결과 화면을 맞춤 설정할 수 있습니다.

developer.mozilla.org

https://velog.io/@kimjumpsun_code/Github%EC%97%90-API-Key-%EC%88%A8%EA%B8%B0%EA%B8%B0

 

Github에 API Key 숨기기

이번 5월 노마드 챌린지를 하기 이전에 나는 이미 api key를 그대로 push 했었다. 그나마 다행인건 private으로 올렸다는 것인데.. 이번 기회에 환경변수를 사용하지 않는 간단한 방법을 알게되어 기

velog.io

 

 

반응형