본문 바로가기
개발/날씨의 아이

[javascript] 바닐라 js로 크롬 앱 만들기

by 박영귤 2023. 9. 10.

js를 배웠으니 간단하게 클론코딩을 해보려고 한다.

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

 

All Courses – 노마드 코더 Nomad Coders

초급부터 고급까지! 니꼬쌤과 함께 풀스택으로 성장하세요!

nomadcoders.co

이 강의를 따라갈 예정이다. 강의를 따라가면서 느끼고 배운 것들을 한 페이지로 요약해서 작성하겠다.

9.10 시작 ~9.17 끝


#2 [2021 UPDATE] WELCOME TO JAVASCRIPT
html에서 js, css파일 가져오는 법
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- css파일 가져옴 -->
    <link rel="stylesheet" href="style.css"> 
    <title>Document</title>
</head>
<body>
    <!-- js파일 가져옴 -->
    <script src="app.js"></script>  
</body>
</html>
상수, 변수
const a = 5; // 상수
let b = 6; // 변수

 always const, somtimes let, never var

null, undefined

null : 값이 없음을 명시(절대 자연적으로 생기지 않음)

undefined : 변수가 메모리는 잡고있지만 값이 할당되지 않은 상태. ex) let a;


#3 [2021 UPDATE] JAVASCRIPT ON THE BROWSER
document

document는 HTML코드와 관련된 객체임.

document에 body, head, title등이 다 저장되어있음.

이렇게 바꿀 수도 있음.

getElementById

getElementById : Id가 일치하는 태그를 불러옴.

h1에도 굉장히 많은 프로퍼티가 있다. document와 마찬가지로 객체임.

querySelector

getElements함수를 css스타일로 작성할 수 있게끔 해주는 함수이다. .hello에서 .(dot)이 클래스 이름으로 검색한다는 의미이다. querySelector는 조건에 맞는 것 한 개만 가져온다. 모두 가져오고 싶다면 querySelectorAll를 사용하면 된다.

addEventListener
let a = document.querySelector(".title");

a.addEventListener('click', function(){
    console.log('clicked');
})

a에 이벤트 리스너를 추가할 수 있다. click이벤트가 발생하면 함수를 실행시켜주는 코드이다.

프로퍼티 중 on으로 시작하는 것들이 바로 이벤트 이름들이다.

a.onclick = function (){
    if (a.style.color === "blue"){
        a.style.color = "red"
    }
    else {
        a.style.color = "blue"
    }
}

이렇게 작성해도 된다.

style은 css에서

style을 js로 변경시키는 것은 좋지 않다. css가 스타일을 위해 생긴 언어인데 js로 변경시킨다면 각자의 역할의 경계가 모호해지기 때문이다.

// app.js
a.addEventListener('click', function (){
    if (a.className == "active"){
        a.className = null
    }
    else {
        a.className = "active"
    }
})
// style.css
h1 {
    color: skyblue;
}

.active {
    color: pink;
}

이런 식으로 바꿀 수 있다.

contain, remove, add

https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList

저렇게 하면 클래스를 통째로 바꿔야한다. 하지만 클래스가 두개, 세개가 있을 경우에는 이것만 빼고 추가하고 싶은 경우가 있다. 그럴 때는 contain, remove, add를 사용하면 된다.

a.addEventListener('click', function (){
    if (a.classList.contains("active")){
        a.classList.remove("active")
    }
    else {
        a.classList.add("active")
    }
})

같은 방식으로 동작한다.

toggle
a.addEventListener('click', function (){
    a.classList.toggle("active")
})

토글을 사용하면 더 간결하게 만들 수 있다.


#4 [2021 UPDATE] LOGIN
input value

input칸에 무언가를 입력하면 value라는 프로퍼티에 담기게 된다.

form
    <form class="login">
        <input type="text" required maxlength="10" placeholder="whats your name?">
        <button>login</button>
    </form>

form대신 div를 사용할 때에는 required가 제대로 작동하지 않았다. 하지만 그 대신 form을 사용하니 로그인 버튼을 눌렀을 때 user에게 메시지를 보내는 것을 볼 수 있다.

input submit
    <form class="login">
        <input type="text" required maxlength="10" placeholder="whats your name?">
        <input type="submit" value="Log in">
    </form>

이전에 button을 추가한 것이랑 같은 모양이지만 조금 다르다. 이것은 form을 submit하는 효과가 있다. form에는 submit이벤트가 있어서 이벤트 리스너를 추가해줄 수 있다. 그리고 submit을 하면 새로고침을 해주는 것이 default이다.

event listener function 인자

이벤트 리스너의 함수에는 인자가 하나 자동으로 들어가있다. 파라미터를 하나 적으면 그 인자의 프로퍼티나 메소드를 사용할 수 있다. 관례적으로 event라고 명시한다.

 preventDefault라는 메소드는 웹브라우저가 새로고침되는 것을 막을 수 있는 메소드이다.

css display

display를 None으로 설정하면 안보이게 설정할 수 있다.

string 표현 두번째 방법
`string ${변수명}`
localStorage (local storage)

js에는 localStorage라는 변수가 이미 정의되어있고 사용할 수 있다.

이러한 메소드들이 있다.

loginForm.addEventListener('submit', function(event){
    const userName = loginInput.value;
    event.preventDefault();
    loginForm.classList.add("hidden");
    loginInput.innerText = userName;
    greeting.innerText = `Hello ${userName}`;
    greeting.classList.remove("hidden");
    // 로컬 저장소에 저장해둘 수 있다.
    localStorage.setItem("name", userName);
    console.dir(localStorage)
})

어플리케이션으로 들어가보면 local 저장소에 뭐가 저장되어있는지 확인할 수 있다.

#4까지 코드
//js
const loginForm = document.querySelector(".login");
const loginInput = document.querySelector(".login input");
const greeting = document.querySelector(".greeting");

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

function paintGreeting(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', function(event){
        const username = loginInput.value;
        event.preventDefault(); // 새로고침 방지
        loginForm.classList.add(HIDDEN_CLASSNAME);
        paintGreeting(username)
        localStorage.setItem(USERNAME_KEY, username);
    })
}
else {
    loginForm.classList.add(HIDDEN_CLASSNAME);
    paintGreeting(savedUsername)
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- css파일 가져옴 -->
    <link rel="stylesheet" href="style.css"> 
    <title>Momentum</title>
</head>
<body>
    <form class="login hidden">
        <input type="text" required maxlength="10" placeholder="whats your name?">
        <input type="submit" value="Log in">
    </form>
    <h1 class="greeting hidden"></h1>
    <!-- js파일 가져옴 -->
    <script src="app.js"></script>  
</body>
</html>
.hidden {
    display: None;
}

#5 [2021 UPDATE] CLOCK
interval

주기적으로 어떤 함수를 시켜주고 싶을 때 interval(인터벌)이란 것을 사용하면 된다. 아래는 설정하는 방법이다.

function f() {
    console.log('1');
}

setInterval(f, 1000); // f함수를 1000ms에 한 번씩 반복시킴
Date
const date = new Date();

date.getFullYear(); // 2023
date.getMonth(); // 8
date.getDate(); // 14
date.getDay(); // 4
date.getHours(); // 21
date.getMinutes(); // 2
date.getSeconds(); // 7
date // Thu Sep 14 2023 21:02:07 GMT+0900 (한국 표준시)

Date 생성자와 그것의 메소드들로 시간을 구할 수 있다.

padStart, padEnd

문자열을 포맷팅할 때 사용할 수 있는 메소드이다. 두 번째 인자를 앞 혹은 뒤에 추가해 첫 번째 인자의 길이로 만들어준다.

"1".padStart(2, "0"); // 01

#6 [2021 UPDATE] QUOTES AND BACKGROUND
createElement

createElement는 js로 html의 element를 하나 생성하는 것이다. 생성한 엘리먼트는 appendChild 함수를 통해 추가할 수 있다.

const images = ['0.jpeg','1.jpeg','2.jpeg','3.jpeg','4.jpeg']

function rand(num) {
    return Math.floor(Math.random() * num)
}

const chosenImage = images[rand(images.length)]

const bgImage = document.createElement("img")

bgImage.src = `img/${chosenImage}`

document.body.appendChild(bgImage)

페이지의 배경 자체를 설정하고 싶으면 body의 background를 설정해주면 된다.

const images = ['0.jpeg','1.jpeg','2.jpeg','3.jpeg','4.jpeg']

function rand(num) {
    return Math.floor(Math.random() * num)
}

const chosenImage = images[rand(images.length)]

document.body.background = `img/${chosenImage}`

사이즈를 100%로 맞추고 싶지만 그건 방법 찾는 것이 조금 걸릴 것 같아 일단 패스!


#7 [2021 UPDATE] TO DO LIST
parentNode
<li>
	<span>eat</span>
	<button>❌</button>
</li>

이런 코드가 있을 때 button의 부모 노드는 li이다. x버튼을 누르면 li를 없애는 코드를 만들고 싶다면 아래 코드처럼 eventListener를 추가하면 된다.

    newButton.addEventListener("click", (event) => {
        const targetTodo = event.target.parentNode
        console.log(targetTodo.parentNode)
        targetTodo.remove()
    })

event의 target에 parentNode라는 프로퍼티가 있고, 각 태그는 remove라는 메소드를 가지고 있다.

JSON.stringify, JSON.parse

stringify : json을 문자열로 변환시켜주는 함수이다.

parse : 문자열을 json으로 변환시켜주는 함수이다.

localStorage에는 오로지 string밖에 담을 수 없다. 따라서 배열이나 객체를 담기 위해선 string으로 변경시켜 담아야 한다. 

function saveTodoList()
{
    localStorage.setItem(TODOLIST_KEY, JSON.stringify(savedTodoList))
}

todoList를 문자열로 변경시켜 로컬저장소에 담는 코드이다.

if (localStorage.getItem(TODOLIST_KEY) !== null)
{
    savedTodoList = JSON.parse(localStorage.getItem(TODOLIST_KEY))
    savedTodoList.forEach(savedTodo => {
        paintTodo(savedTodo)
    });
}

로컬 저장소에 있던 todolist를 배열로 변경시켜 savedTodoList 변수에 담는 코드이다.

forEach

위의 코드를 아래처럼 작성해도 같은 방식으로 동작한다.

if (localStorage.getItem(TODOLIST_KEY) !== null)
{
    savedTodoList = JSON.parse(localStorage.getItem(TODOLIST_KEY))
    savedTodoList.forEach(paintTodo)
}

forEach는 savedTodoList의 요소를 하나씩 뽑아 paintTodo함수의 인자로 전달하는 것이다.

요소 삭제

x버튼을 눌러 todo를 삭제시키기 위해서는 각 todo가 고유한 id 를 가지고 있어야 한다. 따라서 로컬 저장소에 todo를 저장할 때 텍스트만 저장하는 것이 아니라, id와 텍스트를 가지고 있는 객체를 저장해야한다. 또한, li의 id도 같은 id를 지정해주면 된다. 

이런 식으로 텍스트가 같아도 id가 다르게끔 설정해주었다. id는 Date.now() 메소드의 리턴값을 저장해주었다.

html코드의 li의 id에도 같은 값을 넣어주었다.

filter
    newButton.addEventListener("click", (event) => {
        const targetTodo = event.target.parentNode
        targetTodo.remove()
        savedTodoList = savedTodoList.filter(newTodoObj => newTodoObj.id !== parseInt(targetTodo.id))
        saveTodoList()
    })

filter의 인자로는 함수를 전달하면된다. 콜백함수는 true 혹은 false를 리턴하는 함수여야한다. filter는 콜백함수의 리턴 값이 true인 것들만 모아서 새로운 배열을 리턴해주는 함수이다.

원래의 배열에는 영향을 끼치지 않는다.


#8 [2021 UPDATE] WEATHER
navigator geolocation getCurrentPosition

현재 위치를 구할 수 있는 함수가 js에 내장되어있다. navigator api를 사용하면 한줄로 구할 수 있다.

getCurrentPosition이라는 함수에 인자로 두 함수를 전달시켜주면 된다. 한 함수는 위치 구하는 것에 성공했을 때 실행시킬 함수, 한 함수는 실패했을 때 실행시킬 함수이다.

function onGeoOK(position) {
    console.dir(position);
}

function onGeoFail() {
    alert("fail");
}

navigator.geolocation.getCurrentPosition(onGeoOK, onGeoFail);

이런 식으로 작성하면 된다. 성공했을 땐 아래와 같이 결과가 출력되는데 latitude, longitude에 위도, 경도가 저장된다.

외부 API

openWeatherMap api를 사용해보자.

레퍼런스 사이트 들어가보면 api 이용 방법이 친절하게 잘 나온다. 이 사용법대로만 해주면 된다.

https://api.openweathermap.org/data/2.5/weather?lat=위도&lon=경도&appid=API_KEY

위도, 경도, api키를 입력시켜주니 아래를 출력해주었다.

{
  "coord":{
    "lon":127.0649,
    "lat":37.4883
  },
  "weather":[
    {
      "id":803,
      "main":"Clouds",
      "description":"broken clouds",
      "icon":"04d"
    }
  ],
  "base":"stations",
  "main":{
    "temp":302.01,
    "feels_like":305.4,
    "temp_min":301.77,
    "temp_max":302.01,
    "pressure":1009,
    "humidity":69
  },
  "visibility":10000,
  "wind":{
    "speed":2.06,
    "deg":190
  },
  "clouds":{
    "all":75
  },
  "dt":1694926858,
  "sys":{
    "type":1,
    "id":5509,
    "country":"KR",
    "sunrise":1694898883,
    "sunset":1694943513
  },
  "timezone":32400,
  "id":6571507,
  "name":"Samjeon-dong",
  "cod":200
}

날씨 정보를 가져오는 모습이다.

fetch

js에서 fetch(url)을 사용하면 아래처럼 비밀스럽게 네트워크통신을 하는 것 같다. js가 url을 대신 불러준다고 생각하면 된다. 여기서 얻어온 정보를 json화시켜서 가공후 사용하면 된다.

preview에 들어가면 url의 응답 결과도 얻어올 수 있다.

fetch를 통해 서버와 통신하는 시간이 조금 걸리기 때문에, then이라는 메소드를 사용해야한다. 통신 후에 실행해라!라는 의미로 사용한다.

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}`
    fetch(url).then(response => response.json()).then(data => {
        console.log(data.name)
        console.log(data.weather[0].main)
        console.log(data.main.temp)
    })
}

이 코드의 결과값은 다음과 같이 나온다. (화씨라 302도로 나옴)

데이터를 잘 가공해서 아래처럼 날씨를 페이지에 띄울 수 있다!


끝! 기본적인 js 이용방법에 대해 완벽히 익힌 느낌이다. 다음 강의로 가자