
👉🏻 25조 아이들 미니프로젝트 사이트
👉🏻 Github 링크
팀 프로젝트에서 메인 페이지인 index.html, 개인 페이지인 personpage.html이 존재한다.
personpage.html에서 보이는 이름, MBTI, 개인블로그, 자기소개에 대한 내용들을 4명의 페이지를 각각 만든 것이 아니라, 해당 내용들을 firebase에 저장을 해놓고 동적으로 가져온다.
각 팀원의 상세 내용을 동적으로 가져오려면 팀원들을 구별할 key가 있어야하는데 코드를 자세히 보면서 설명하도록 하겠다.
다음 코드는 index.html에서 script 태그 안에 있다.
// 팀원 리스트
try {
// 컬렉션명 셋팅
let colectionName = "we-are-i";
// Firebase 인스턴스 초기화
let app = initializeApp(firebaseConfig);
let db = getFirestore(app);
let docs = await getDocs(collection(db, colectionName));
docs.forEach((doc) => {
let row = doc.data();
let url = "we-are-i/personpage.html?userKey=" + doc.id;
let memberTemplate = `<div class="col">
<div class="card h-100">
<img src="${row.image}" class="card-img-top" alt="..." style="height : 200px">
<div class="card-body">
<h5 class="card-name">${row.name}</h5>
<p class="card-mbti">${row.mbti}</p>
<a href="/${url}" target="_self" class="btn btn-secondary mt-2">더보기</a>
<div class="additional-info mt-2" style="display: none;">
</div>
</div>
</div>
</div>`;
let membersNameTemplate = `<li><a href="/${url}" class="nav-link px-2 text-black">${row.name}</a></li>`;
$('#memberList').append(memberTemplate);
$('#membersNameList').append(membersNameTemplate)
});
} catch (e) {
console.log("페이지 오류 발생 : " + e.message)
alert("페이지에 치명적인 오류가 발생하였습니다. 죄송합니다.")
}
위 코드는 we-are-i라는 collection에서 정보를 가져와 메인 페이지의 팀원의 카드와 상단 네비게이션 바를 구현한 부분이다.
📌 Collection 지정
// 컬렉션명 셋팅
let colectionName = "we-are-i";
정보를 가져올 collection인 "we-are-i"를 colectionName에 할당한다.
📌 Firebase 인스턴스 초기화
// Firebase 인스턴스 초기화
let app = initializeApp(firebaseConfig);
let db = getFirestore(app);
initializeApp(firebaseConfig)
: Firebase 애플리케이션을 초기화한다. firebaseConfig는 Firebase 프로젝트 설정에서 얻을 수 있는 구성 Object(객체)이다.
getFirestore(app)
: 초기화된 app에서 Firestore db 인스턴스를 가져온다.
📌 Firebase에서 문서 가져오기
let docs = await getDocs(collection(db, colectionName));
collection(db, colectionName)
: 지정된 collection인 "we-are-i"를 참조한다.
await getDocs(...)
: 지정된 collection의 모든 문서를 가져온다. await를 사용하여 getDocs(...)의 실행이 끝날 때까지 기다린다.(=> 비동기 작업)
👀 await이란?
javascript는 실행될 때 코드 첫번째줄의 실행이 완료된 후 다음줄을 실행하는 것이 아니라, 처음부터 끝까지 완료여부에 상관없이 순서대로 실행만 한다.
하지만 await을 앞에 붙이게 되면 ex) await getDocs(...) getDocs의 실행이 모두 완료된 후 다음줄을 실행한다.
docs의 자료형은 Firestore에서 반환된 QuerySnapshot 객체이다.
이 QuerySnapshot 객체는 여러 개의 QueryDocumentSnapshot 객체를 포함한다.
👀 QuerySnapshot 객체인 docs의 구조?
'docs': 'QueryDocumentSnapshot' 객체들의 배열 ➡️ 이 'docs'는 QuerySnapshot 객체인 docs의 한 속성이다.
'empty': 쿼리 결과가 비어있는지 여부를 나타내는 bool 값
'size': 쿼리 결과에 포함된 문서의 수
'forEach': 각 'QueryDocumentSnapshot'에 대해 주어진 함수를 호출

📌 각 문서에 대해서 반복문 실행
docs.forEach((doc) => {
let row = doc.data();
forEach 메서드는 QuerySnapshot 객체인 docs에 포함된 각 QueryDocumentSnapshot 객체(doc)를 순회한다.
doc은 QueryDocumentSnapshot 객체로 해당 문서의 데이터를 가져오기 위해 data() 메서드를 호출할 수 있다.
👀 QueryDocumentSnapshot 객체인 doc의 주요 메서드
data(): 문서의 데이터를 객체 현태로 반환한다.
id: 문서의 고유 ID를 반환한다.
exists: 문서가 존재하는지 여부를 나타내는 bool 값

📌 url 생성
let url = "we-are-i/personpage.html?userKey=" + doc.id;
doc.id: 현재 문서의 ID를 가져온다.
personpage.html로 이동할때,firebase에 있는 팀원의 상세정보들을 동적으로 가져온다고 했었다. 만약 홍길동씨의 정보를 가져오려면 저 firebase에 어떤 doc이 홍길동씨인건지 알아야한다.
👀 어떻게?
=> 이때 사용하는 것이 doc.id이다. doc은 자신을 다른 doc들과 다르다는 것을 식별해주는 id를 가지고 있다. 즉, id를 통해 각 팀원의 정보를 가져올 수 있는 것이다.
이 url은 팀원의 각 페이지로 넘어가는 url을 의미한다. 홍길동씨의 유저키가 1234라고 해보자. 그러면 "we-are-i/personpage.html?userKey=1234"의 url로 이동하면 홍길동씨의 개인 페이지로 이동하는 것이다.
📌 memberTemplate 생성 for Card 형식
이 memberTemplate은 사이트의 팀원 카드 template이다.
let memberTemplate = `<div class="col">
<div class="card h-100">
<img src="${row.image}" class="card-img-top" alt="..." style="height : 200px">
<div class="card-body">
<h5 class="card-name">${row.name}</h5>
<p class="card-mbti">${row.mbti}</p>
<a href="/${url}" target="_self" class="btn btn-secondary mt-2">더보기</a>
<div class="additional-info mt-2" style="display: none;">
</div>
</div>
</div>
</div>`;
firebase에 있는 각 팀원의 정보들을 doc으로 가져오고 이를 row = doc.data()를 통해 row에 객체 형태로 저장하였다. 아래 사진은 we-are-i collection 안의 한 문서이며, 각 field에 해당되는 내용 또한 저장되어있다.

따라서, 코드를 다시 뜯어보면
<img src="${row.image}" class="card-img-top" alt="..." style="height : 200px">
row.image는 image field의 값일 것이고,
<h5 class="card-name">${row.name}</h5>
row.name은 name field의 값,
<p class="card-mbti">${row.mbti}</p>
row.mbti는 mbti field의 값일 것이다.
<a href="/${url}" target="_self" class="btn btn-secondary mt-2">더보기</a>
이는 더보기를 눌렀을 때, 위에서 생성한 url로 이동하도록 하는 코드이다.
📌 membersNameTemplate 생성 for 상단 nav 바
이 membersNameTemplate은 상단 네비게이션 바에 각 팀원의 이름을 누르면 개인 페이지로 이동할 수 있도록 하는 template이다.
let membersNameTemplate = `<li><a href="/${url}" class="nav-link px-2 text-black">${row.name}</a></li>`;
위에서 생성한 각 팀원의 url이 있어 이름을 누르면 각 페이지로 이동한다.
row.name은 name field의 값이다.
📌 각 List에 추가하기
$('#memberList').append(memberTemplate);
memberList id를 가진 곳에 위에서 선언한 memberTemplate을 추가한다. 이는 팀원 카드 리스트 부분에 팀원들의 정보를 가진 카드를 추가하는 것이다.
$('#membersNameList').append(membersNameTemplate)
membersNameList id를 가진 곳에 위에서 선언한 membersNameTemplate을 추가한다. 상단 네비게이션에 클릭하면 개인 페이지로 넘어가는 팀원의 이름을 추가한 것이다.
메인 페이지인 index.html에서 카드 형식에서 더보기를 누르거나 상단 네비게이션 바에 있는 팀원의 이름을 클릭하면 personpage.html?userKey= 인 팀원의 개인페이지로 넘어가는 flow를 갖고 있다.
그래서, 이번에는 personpage.html 파일에서 어떻게 팀원들의 상세 정보를 동적으로 가져오는지 알아보도록 하겠다. 다음은 personpage.html의 전체 script 코드이다.
<script type="module">
// 테스트 용도
let testPrimaryKey = "zcu81cQyDqZhagXlUqyR"
// 컬렉션명 셋팅
let colectionName = "we-are-i";
// 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, doc, addDoc, updateDoc, deleteDoc } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
import { getDoc, getDocs } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
// Firebase 구성 정보 설정
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "AIzaSyDSL5LX_2P0Er02VRH7KRP017Xo8hoKHAo",
authDomain: "sparta-25f43.firebaseapp.com",
projectId: "sparta-25f43",
storageBucket: "sparta-25f43.appspot.com",
messagingSenderId: "509060407358",
appId: "1:509060407358:web:02fabae90c30c4418cf16f",
measurementId: "G-NSQY74906J"
};
// 상세페이지의 팀원 정보
let userData = "";
// 우리팀원들 이름 배열
let ourMembersName = [];
try {
// Firebase 인스턴스 초기화
let app = initializeApp(firebaseConfig);
let db = getFirestore(app);
// url 정보
let urlStr = window.location.href;
// url 객체화
let url = new URL(urlStr);
// url queryParam 추출
const urlParams = url.searchParams;
// 우리팀원의 고유 id
let primaryKey = typeof urlParams.get('userKey') != "undefined" && urlParams.get('userKey') != null ? urlParams.get('userKey') : testPrimaryKey;
if (primaryKey == "") alert("잘못된 경로입니다.")
let docs = await getDocs(collection(db, colectionName));
docs.forEach((doc) => {
let row = doc.data();
var userDataObject = {
"name" : row['name'],
"userKey" : doc.id
}
ourMembersName.push(userDataObject);
if (primaryKey == doc.id) {
userData = row;
}
});
ourMembersName.forEach(data => {
let url = window.location.pathname + "?userKey=" + data.userKey;
$('#members_name').append(`<li><a href="${url}" class="nav-link px-2 text-black">${data.name}</a></li>`)
})
Object.keys(userData).forEach(data => {
if(data == 'image'){
$('#' + data).attr("src" , userData[data]);
}else{
$('#' + data).text(userData[data]);
}
})
} catch (e) {
console.log("페이지 오류 발생 : " + e.message)
alert("페이지에 치명적인 오류가 발생하였습니다. 죄송합니다.")
}
</script>
📌 Firebase 기본 설정하기
<script type="module">
// 테스트 용도
let testPrimaryKey = "zcu81cQyDqZhagXlUqyR"
// 컬렉션명 셋팅
let colectionName = "we-are-i";
// 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, doc, addDoc, updateDoc, deleteDoc } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
import { getDoc, getDocs } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
// Firebase 구성 정보 설정
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "AIzaSyDSL5LX_2P0Er02VRH7KRP017Xo8hoKHAo",
authDomain: "sparta-25f43.firebaseapp.com",
projectId: "sparta-25f43",
storageBucket: "sparta-25f43.appspot.com",
messagingSenderId: "509060407358",
appId: "1:509060407358:web:02fabae90c30c4418cf16f",
measurementId: "G-NSQY74906J"
};
</script>
script type="module"을 사용해야 firebase를 사용할 수 있다.
colectionName은 "we-are-i"로 설정해주었다.
📌 변수 선언
// 상세페이지의 팀원 정보
let userData = "";
// 우리팀원들 이름 배열
let ourMembersName = [];
userData: 나중에 firebase에서 가져올 팀원의 상세 정보
ourMembersName: 나중에 Nav 바를 구현할 때 사용할 팀원 이름 리스트
📌 try 블럭
try {
// ...
} catch (e) {
console.log("페이지 오류 발생 : " + e.message);
alert("페이지에 치명적인 오류가 발생하였습니다. 죄송합니다.");
}
오류 발생시 catch 블록으로 이동
📌 firebase 인스턴스 초기화
// Firebase 인스턴스 초기화
let app = initializeApp(firebaseConfig);
let db = getFirestore(app);
initializeApp(firebaseConfig)
: Firebase 애플리케이션을 초기화한다. firebaseConfig는 Firebase 프로젝트 설정에서 얻을 수 있는 구성 Object(객체)이다.
getFirestore(app)
: 초기화된 app에서 Firestore db 인스턴스를 가져온다.
📌 현재 개인 페이지의 url 가져오기
// url 정보
let urlStr = window.location.href;
현재 페이지의 url을 가져온다.
자료형은 string이다.
📌 현재 개인 페이지의 url 객체로 변환
// url 객체화
let url = new URL(urlStr);
위에서 가져온 url string을 객체로 변환한다.
첫번째는 urlStr(주소 string)이고 두번째는 url(주소 객체)이다.

📌 url queryParam 추출
// url queryParam 추출
const urlParams = url.searchParams;
url의 쿼리 매개변수를 추출한다.
personpage.html?userKey=NESfX3Lmskx1X3TpMYZu
주소에서 userKey는 매개변수(parameter)인데 이 값을 가져올 때 사용된다.
📌 팀원의 고유 id 찾기
👀 A ? B : C
A의 조건이 참이면 B 아니면 C를 반환한다.
아래 코드는 A ? B : C 형식을 가졌다.
let primaryKey = typeof urlParams.get("userKey") != "undefined" && urlParams.get("userKey") != null? urlParams.get("userKey"): testPrimaryKey;
if (primaryKey == "") alert("잘못된 경로입니다.");
primaryKey를 할당한 부분이 상당히 길다. 차근차근 뜯어보자.
typeof urlParams.get("userKey") != "undefined"
urlParams은 위에서 가져온 쿼리 매개변수이다.get이라는 메서드를 쓸수 있는데 이는 urlParams.get("useKey")는 url의 매개변수 중 userKey라는 매개변수의 값을 가져와주세요라는 뜻이다.
그리고 앞에 typeof가 붙었기 때문에 userKey 매개변수의 값의 자료형을 의미한다.
따라서, 이 코드 부분은 userKey 매개변수의 값의 자료형이 undefined이 아닐 경우를 의미한다.
urlParams.get("userKey") != null
위에서 봤던 코드이다. urlParams.get("userKey")는 userKey 매개변수의 값을 가져와주세요라는 뜻이다.
그리고, 전체 이 코드의 뜻은 userKey 매개변수의 값이 null 값이 아닌 경우를 의미한다.
let primaryKey = typeof urlParams.get("userKey") != "undefined" && urlParams.get("userKey") != null? urlParams.get("userKey"): testPrimaryKey;
따라서 이 긴 코드는 userKey 매개변수 자료형이 undefined이 아니거나, userKey의 값이 null값이 아닐때 userKey 매개변수의 값을 primaryKey에 할당하고, 아닌 경우 testPrimaryKey를 할당하는 코드이다.
if (primaryKey == "") alert("잘못된 경로입니다.");
이 코드는 위에서 할당받은 primaryKey의 값이 ""값이면 잘못된 경로라는 메세지를 alert 창으로 띄운다.
📌 Firebase에서 문서 가져오기
let docs = await getDocs(collection(db, colectionName));
지정된 collection인 colectionName("we-are-i")에서 모든 문서를 가져온다.
docs는 QuerySnapshot 객체이며 QueryDocumentSnapshot 객체들의 배열인 docs를 속성으로 가진다.
📌 문서를 순회하며 ourMembersName에 원소 추가하기
➡️ 전체코드
docs.forEach((doc) => {
let row = doc.data();
var userDataObject = {
"name" : row['name'],
"userKey" : doc.id
}
ourMembersName.push(userDataObject);
if (primaryKey == doc.id) {
userData = row;
}
});
➡️ 뜯어보기
docs.forEach((doc) => {
let row = doc.data();
forEach 메서드는 QuerySnapshot 객체인 docs의 속성 docs 배열에 있는 각 QueryDocumentSnapshot 객체(doc)를 순회한다.
doc은 QueryDocumentSnapshot 객체로 해당 문서의 데이터를 가져오기 위해 data() 메서드를 호출할 수 있다.
그 데이터를 row에 할당한다.
var userDataObject = {
"name" : row['name'],
"userKey" : doc.id
}
userDataObject = {} 객체가 있다. 키로는 "name", "userKey"를 가진다.
name field의 값을 "name" 키에 할당한다.
doc.id를 "userKey" 키에 할당한다.
doc은 docs에 있는 QueryDocumentSnapShot 객체인 문서이고 이 객체의 메서드 중 문서의 고유 ID를 반환하는 메서드인 id를 사용하여 "userKey" 키값에 문서 고유 아이디를 할당해주고 있다.
따라서, 팀원의 이름과 팀원 상세 정보를 갖고 있는 문서의 고유 키를 객체 형태로 선언한 부분인 것이다.

ourMembersName.push(userDataObject);
이렇게 팀원과 고유키를 담은 userDataObject 객체를 ourMembersName 배열에 원소로 추가해준다. 이는 이후 personpage.html에서 상단 네비게이션 바에 추가될 이름 목록과 이름을 클릭하면 해당 Id을 담고 있는 배열이 된다.
if (primaryKey == doc.id) {
userData = row;
}
url에서 가져온 userKey 매개변수의 값을 할당한 primaryKey 변수의 값이 현재 순회하는 doc의 id 값과 일치하면 나중에 개인 페이지로 이동하고 팀원의 상세 정보를 불러올 때 사용될 userData 변수에 현재 doc의 data 값인 row를 할당해준다.
즉, 첫번째 url 사진의 userKey= 뒤에 있는 값과 두번째 firebase에서 문서의 고유 key 값이 같은 경우에만 해당 문서의 데이터를 userData에 할당하겠다는 뜻이다.


📌 팀원 이름 상단 네비게이션 바에 추가하기
➡️ 전체코드
ourMembersName.forEach(data => {
let url = window.location.pathname + "?userKey=" + data.userKey;
$('#members_name').append(`<li><a href="${url}" class="nav-link px-2 text-black">${data.name}</a></li>`)
})
팀원과 고유키를 담은 userDataObject 객체를 원소로 지닌 ourMembersName를 순회하며 네비게이션 바에 팀원의 이름을 추가하고 팀원의 이름을 누르면 해당 개인 페이지로 이동할 수 있도록 주소를 생성하는 코드이다. data는 ourMembersName의 원소를 순회할 때 쓰이며 이는 userDataObject 객체이다.
➡️ 뜯어보기
let url = window.location.pathname + "?userKey=" + data.userKey;
url을 정의하는데 window.location.pathname이 있다.
👀 window.location.pathname 이란?
현재 URL의 경로 부분만 반환한다.
경로 부분이란 도메인 이름 및 포트 번호 뒤에 오는 부분이다.
예를 들어, http://localhost:8080/home 라는 url이 있으면
window.location.pathname의 값은 /home 이다.
우리의 개인페이지 주소인 "https://idle-project.github.io/we-are-i/personpage.html?userKey=" 뜯어보면 다음 사진과 같고,

window.location.pathname의 값은 경로인 /we-are-i/personpage.html이 되는 것이다.
let url = window.location.pathname + "?userKey=" + data.userKey;
다시 코드를 보면, url은 현재 개인 페이지의 경로 값을 반환하는 window.location.pathname 더하기 쿼리 매개변수인 ?userKey= 와 userKey 값인 data.userKey를 더한 것이다. data는 userDataObject 객체이며 원소로 팀원의 이름인 name과 각 팀원의 문서의 고유키인 userKey 값을 가지고 있다.
$('#members_name').append(`<li><a href="${url}" class="nav-link px-2 text-black">${data.name}</a></li>`)
members_name을 id로 가진 태그 즉, 네비게이션 바에 위에서 선언한 url을 하이퍼링크로 가지고 data.name 즉 팀원의 이름은 보여주도록 하는 html 코드를 추가해준다. 즉, 네비게이션 바에 클릭하면 해당 팀원의 개인 페이지 url로 이동할 수 있도록 하는 이름을 추가해주는 코드인 것이다!
📌 팀원의 개인 페이지에 상세 정보 가져오기
➡️ 전체코드
Object.keys(userData).forEach(data => {
if(data == 'image'){
$('#' + data).attr("src" , userData[data]);
}else{
$('#' + data).text(userData[data]);
}
})
➡️ 뜯어보기
먼저, 반복문부터 살펴보자.
Object.keys(userData).forEach(data => {...});
👀 Object.keys(객체) 메서드
: 객체의 속성 키로 이루어진 배열을 반환한다.
forEach() 메서드
: 반환된 배열의 각 요소에 대해 콜백 함수 {...}를 실행한다.
즉, 객체의 속성 키를 순회는 것이다.
userData는 현재 personpage.html url의 userKey 매개변수 값과 firebase의 문서의 고유 key 값(doc.id)가 같은 경우 해당 doc.data()를 저장해놓은 객체이다.

즉, 코드는 userData 객체의 키값을 순회하는 코드이다.
if(data == 'image'){
$('#' + data).attr("src" , userData[data]);
}else{
$('#' + data).text(userData[data]);
}
data는 userData의 키 값을 순회하는데 쓰이는 변수이고 즉, data는 키 값이다.
이러한 data가 image일 경우, id의 값이 data 즉, image인 태그의 속성 중 src 속성을 userData 객체의 image 키에 해당되는 값으로 설정한다.
그리고, 다른 경우 id가 data 즉 키 값을 가지고 있는 경우 그에 대한 text 값으로 userData의 해당 data 즉 키에 해당하는 값으로 할당한다는 의미이다.


