[12/24] TIL

2024. 12. 24. 19:58개발 회고/TIL

😊오늘 배운 내용

웹개발 종합반 강의 3주차 ~ 4주차

전체적으로 jquery, fetch, firebase firestroe 연동하기 를 배웠다.

 

[album 코드]

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
        crossorigin="anonymous"></script>

    <title>나만의 추억앨범</title>

    <style>
        @import url('https://fonts.googleapis.com/css2?family=Black+Han+Sans&display=swap');

        * {
            font-family: "Black Han Sans", sans-serif;
            font-weight: 400;
            font-style: normal;
        }

        .mytitle {
            color: white;
            height: 300px;

            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;

            background-image: url('https://images.unsplash.com/photo-1511992243105-2992b3fd0410?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80');
            background-position: center;
            background-size: cover;
        }

        .mytitle>button {
            width: 150px;
            height: 50px;
            background-color: transparent;
            border: 1px solid white;
            border-radius: 10px;
            color: white;
            margin-top: 20px;
        }

        .mycards {
            width: 1200px;
            margin: 30px auto 30px auto;
        }

        .mypostingbox {
            width: 500px;
            margin: 30px auto 30px auto;
            padding: 20px;
            box-shadow: 0px 0px 3px 0px gray;
            border-radius: 5px;
        }

        .mybtn {
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;
        }

        .mybtn>button {
            margin-right: 5px;
        }
    </style>

    <script type="module">
        // Firebase SDK 라이브러리 가져오기
        import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js";
        import { getFirestore } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
        import { collection, addDoc } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
        import { getDocs } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";


        // For Firebase JS SDK v7.20.0 and later, measurementId is optional
        const firebaseConfig = {
        
        };


        // Firebase 인스턴스 초기화
        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);

        
        $("#postingbtn").click(async function () {
            let image = $('#image').val();
            let title = $('#title').val();
            let content = $('#content').val();
            let date = $('#date').val();

            let doc = {
                'image': image,
                'title': title,
                'content': content,
                'date': date
            };

            await addDoc(collection(db, "albums"), doc);

            alert('기록 완료!');
            window.location.reload();

        })

        $("#savebtn").click(async function () {
            $('#postingbox').toggle();
        })


        let url = "http://spartacodingclub.shop/sparta_api/seoulair";
        fetch(url).then(res => res.json()).then(data => {
            let mise = data['RealtimeCityAir']['row'][0]['IDEX_NM'];
            $('#msg').text(mise);
        })

        let docs = await getDocs(collection(db, "albums"));
        docs.forEach((doc) => {
            let row = doc.data();
            console.log(row);

            let image = row['image'];
            let title = row['title'];
            let content = row['content'];
            let date = row['date'];

            let temp_html = `
            <div class="col">
                <div class="card h-100">
                    <img src="${image}"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">${title}</h5>
                        <p class="card-text">${content}</p>
                    </div>
                    <div class="card-footer">
                        <small class="text-muted">${date}</small>
                    </div>
                </div>
            </div>`;

            $('#card').append(temp_html);

        });

    
    </script>
</head>

<body>
    <div class="mytitle">
        <h1>나만의 추억앨범</h1>
        <p>현재 서울의 미세먼지 : <span id="msg">좋음</span></p>
        <button id="savebtn">추억 저장하기</button>
    </div>
    <div class="mypostingbox" id="postingbox">
        <div class="form-floating mb-3">
            <input type="email" class="form-control" id="image" placeholder="앨범 이미지">
            <label for="floatingInput">앨범 이미지</label>
        </div>
        <div class="form-floating mb-3">
            <input type="email" class="form-control" id="title" placeholder="앨범 제목">
            <label for="floatingInput">앨범 제목</label>
        </div>
        <div class="form-floating mb-3">
            <input type="email" class="form-control" id="content" placeholder="앨범 내용">
            <label for="floatingInput">앨범 내용</label>
        </div>
        <div class="form-floating mb-3">
            <input type="email" class="form-control" id="date" placeholder="앨범 날짜">
            <label for="floatingInput">앨범 날짜</label>
        </div>
        <div class="mybtn">
            <button id="postingbtn" type="button" class="btn btn-dark">기록하기</button>
            <button type="button" class="btn btn-outline-dark">닫기</button>
        </div>
    </div>
    <div class="mycards">
        <div id="card" class="row row-cols-1 row-cols-md-4 g-4">
            <div class="col">
                <div class="card h-100">
                    <img src="https://images.unsplash.com/photo-1446768500601-ac47e5ec3719?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1446&q=80"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">앨범 제목</h5>
                        <p class="card-text">앨범 내용</p>
                    </div>
                    <div class="card-footer">
                        <small class="text-muted">앨범 날짜</small>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card h-100">
                    <img src="https://images.unsplash.com/photo-1446768500601-ac47e5ec3719?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1446&q=80"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">앨범 제목</h5>
                        <p class="card-text">앨범 내용</p>
                    </div>
                    <div class="card-footer">
                        <small class="text-muted">앨범 날짜</small>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card h-100">
                    <img src="https://images.unsplash.com/photo-1446768500601-ac47e5ec3719?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1446&q=80"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">앨범 제목</h5>
                        <p class="card-text">앨범 내용</p>
                    </div>
                    <div class="card-footer">
                        <small class="text-muted">앨범 날짜</small>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card h-100">
                    <img src="https://images.unsplash.com/photo-1446768500601-ac47e5ec3719?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1446&q=80"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">앨범 제목</h5>
                        <p class="card-text">앨범 내용</p>
                    </div>
                    <div class="card-footer">
                        <small class="text-muted">앨범 날짜</small>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>

</html>

 

[jquery]

컴포넌트를 사라졌다가 나타나게 할 수있는 .toggle()과 태그의 텍스트를 바꿀 수 있는 .text() 함수를 배웠다. 

 

[fetch]

let url = "http://spartacodingclub.shop/sparta_api/seoulair";
        fetch(url).then(res => res.json()).then(data => {
            let mise = data['RealtimeCityAir']['row'][0]['IDEX_NM'];
            $('#msg').text(mise);
        })

fetch의 기본 구조로 url을 불러온다음 response로 받아온 데이터를 json으로 받아서 데이터의 키값과 인덱스로 접근해서 사용한다.

 

[새로고침]

사진 정보를 입력한다음 기록하기를 누르면 내가 입력한 데이터를 가지고 카드를 만들어서 생성하고 내가 입력한 정보는 사라지게 만들고 싶다. 그럴때는 새로고침을 해주면 된다. 

alert('기록 완료!');
window.location.reload();

 

[firebase 연동]

script 태그를 다음과 같이 수정해준다. 해당 코드의 의미는 페이지를 다 로드 한 후 스크립트 코드를 실행하겠다는 의미다. 

<script type="module">

 

다음과 같이 firbase를 연동해준다. 

// Firebase SDK 라이브러리 가져오기
        import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js";
        import { getFirestore } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
        import { collection, addDoc } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
        import { getDocs } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";


        // For Firebase JS SDK v7.20.0 and later, measurementId is optional
        const firebaseConfig = {
            //본인 설정에 맞게 입력
            //프로젝트 설정 -> 내 앱 -> 구성
        };


        // Firebase 인스턴스 초기화
        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);

 

[.click()]

script 태그에 type="module"설정을 해주면 onclick이 동작하지 않게 된다. 그래서 자바스크립트로 click을 직접 구현해주어야 한다.

$("#postingbtn").click(async function () {
            let image = $('#image').val();
            let title = $('#title').val();
            let content = $('#content').val();
            let date = $('#date').val();

            let doc = {
                'image': image,
                'title': title,
                'content': content,
                'date': date
            };

            await addDoc(collection(db, "albums"), doc);

            alert('기록 완료!');
            window.location.reload();

        })

위와 같이 특정 태그를 id로 지정하고 해당 태그를 클릭 했을 시 ~와 같이 동작한다고 구현한다.

 

[addDoc]

            let image = $('#image').val();
            let title = $('#title').val();
            let content = $('#content').val();
            let date = $('#date').val();

            let doc = {
                'image': image,
                'title': title,
                'content': content,
                'date': date
            };

            await addDoc(collection(db, "albums"), doc);

input 태그로 입력받은 값들을 .val()로 얻어온다. 딕셔너리로 데이터 구조를 정의한다. addDoc 함수를 통해서 firbase 데이터베이스에 albums라고 저장한 데이터베이스에 저장한다. 

 

[async, await]

이 코드를 기반으로 async, await을 설명하자면,

$("#postingbtn").click(async function () {
            let image = $('#image').val();
            let title = $('#title').val();
            let content = $('#content').val();
            let date = $('#date').val();

            let doc = {
                'image': image,
                'title': title,
                'content': content,
                'date': date
            };

            await addDoc(collection(db, "albums"), doc);

            alert('기록 완료!');
            window.location.reload();

        })

 

async는 이 함수는 비동기 작업이 포함되어 있다고 선언하는 뜻이다. 원래는 async 함수는 Promise를 반환한다. 그리고 await는 async 함수 내부에서만 사용할 수 있으며, Promise가 해결될 때까지 기다린다. 

async function fetchData() {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();
    console.log(data);
}

fetchData();

즉 fetch를 하면서 비동기 작업이 시작되고 fetch 작업이 끝날때까지 코드를 실행중지하고 기다린다. 그리고 또 response를 받을 때까지 기다린 후, 다음 코드가 실행된다. 쉬운말로 하면 await 다음 코드들에게 이 코드 먼저 실행 후 너희들이 실행되야하니 기다리라는 의미이다. 

 

await을 사용하지 않는다면

addDoc(collection(db, "albums"), doc);  // 저장 시작
alert('기록 완료!');  // 저장이 완료되기 전에 실행될 수 있음
window.location.reload();  // 페이지가 새로고침되어 데이터가 누락될 수 있음

데이터베이스로 값을 넘기기 전에 alert메세지를 띄울 수도 있고 그 전에 페이지가 새로고침되어 내가 데이터베이스로 넘긴 값이 반영이 되지 않고 페이지가 새로고침될 수 있다.  

 

[getDocs]

        let docs = await getDocs(collection(db, "albums"));
        docs.forEach((doc) => {
            let row = doc.data();
            console.log(row);

            let image = row['image'];
            let title = row['title'];
            let content = row['content'];
            let date = row['date'];

            let temp_html = `
            <div class="col">
                <div class="card h-100">
                    <img src="${image}"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">${title}</h5>
                        <p class="card-text">${content}</p>
                    </div>
                    <div class="card-footer">
                        <small class="text-muted">${date}</small>
                    </div>
                </div>
            </div>`;

            $('#card').append(temp_html);

        });

 

addDoc를 이용해 값을 데이터베이스로 넘겼다면 getDocs를 이용해 데이터베이스에 값을 가져온다. albums 데이터베이스에서 값을 가져오기까지 기다린 후 foreach문을 통해 값을 담은 row변수를 키값으로 접근하여 각각의 변수에 저장한다. 그리고 만들어서 붙일 카드 html 코드를 ``에 담고 데이터베이스에서 가져온 값으로 바뀌어야할 부분은 ${}로 감싸준다. 그리고 해당 카드들이 들어갈 div태그의 id값을 지정하고 .append() 해준다. 

 

[화살표함수]

docs.forEach((doc) => {
let row = doc.data();
console.log(row);
...

자바스크립트에서 화살표함수를 볼 수 있다. 구조는 함수이름( (매개변수) => {함수내용}); 과 같다.

 


[어떤 문제가 있었는지]

CORS에러 : 

fetch(url).then(res => res.json()).then(data => {

fetch를 할 때 다음과 같이 쓰는데 url을 써야할 부분을 'url'로 하니 CORS에러가 났었다. 단순한 오타 문제였지만 해결하다가 구글링을 보니 동일한 출처에서 fetch해오지 않을 때 발생하는 것 같다. 잘은 모르지만 나중에 볼 수도 있을 것 같은 에러 같았다...

 

html 줄바꿈:

3주차 강의의 숙제를 하다가 

<p class="col-md-8 fs-4">본인만의 맛집을 소개하는 사이트입니다.<br>맛집을 소개해주세요!</p>

두 문장을 줄바꿈해야하는 요구사항이 있었다. 처음엔 어떻게 하는지 몰라 p태그 안에서 엔터를 쳤었다.

 

[내가 시도해본 것들]

CORS에러는 튜터님의 도움을 받았다. 줄바꿈 문제는 p태그 안에서 엔터도 쳐보고 p태그를 2개를 만들기도 해보았다. 그런데 줄 사이의 간격이 너무 멀어서 이게 정답이 아닌 것 같았다.

 

[어떻게 해결했는지]

구글링을 통해서 <br>태그의 존재를 알았다. <br>태그는 닫음태그같은 것 없이 그냥 줄바꿈 하고 싶은 문장과 문장사이에 넣어주면 된다. 

 

[무엇을 새롭게 알았는지]

오늘은 jquery, fetch, firebase연동, async, await에 대해서 알게되었다. <br>태그도 알게 되었다. 이제 앞으로 개인 홈페이지를 내 스스로 만들어보면서 배운 것들을 익혀야겠다.😊