로그인 후 변화해야 하는 것 (2)
내 물건 팔기 기능 완성
asw 가이드를 통해 세팅을 한다(가이드 따로 만들었음).
클라이언트 쪽 코드
< div class = "insert_bg" >
< div class = "insert_container" >
< div class = "insert_close" > ⨉ </ div >
< img class = "login_img" src = "./img/logo.png" alt = "" >
< p > 간편하게 판매하세요 </ p >
< form action = "/insert" method = "post" enctype = "multipart/form-data" >
< input class = "id" name = "title" id = "insert_title" type = "text" maxlength = "20" minlength = "1" placeholder = "제목을 작성하세요" >
< input class = "pw" name = "text" id = "insert_text" type = "text" maxlength = "50" minlength = "1" placeholder = "내용을 작성하세요" >
< input class = "pw" name = "price" id = "insert_price" type = "text" maxlength = "10" minlength = "1" placeholder = "가격을 작성하세요(원 단위)" >
< input id = "insert_img" name = "img" type = "file" accept = "image/*" >
< input class = "submit" id = "insert_submit" type = "submit" value = "올리기" >
</ form >
</ div >
</ div >
(js)
$ ( '#insert_submit' ). click ( function (){ // 물건 제출 버튼 누르면
if ( $ ( '#insert_title' ). val ()!= '' && $ ( '#insert_text' ). val ()!= '' && $ ( '#insert_price' ). val ()!= '' && $ ( '#insert_img' ). val ()!= '' ){ // 셋다 공백이 아니면
alert ( '판매글을 게시했습니다.' )
}
else { alert ( '전부 입력해야 판매글을 등록할 수 있습니다.' )} // 둘 중 하나라도 공백이면 이거 띄워줌
})
서버쪽 코드
app . post ( '/insert' , upload . single ( 'img' ), async ( 요청 , 응답 ) => {
try {
if ( 요청 . body . title != '' && 요청 . body . price != '' && 요청 . body . text != '' ){
await db . collection ( 'products' ). insertOne ({
user : 요청 . user . _id ,
title : 요청 . body . title ,
price : 요청 . body . price + '원' ,
text : 요청 . body . text ,
img : 요청 . file . location
})
응답 . redirect ( '/' )
}
else {
응답 . redirect ( '/' )
}}
catch ( e ){
응답 . redirect ( '/' )
}
})
로그인 후 변화해야 하는 것(3)
3. 채팅 기능이 동작할 것 => '채팅'에 필요한 데이터는 내 정보, 주고받은 채팅 내용
<소켓 통신 배우고 할 것임 ㅜㅜ>
로그인 후 변화해야 하는 것(4)
4. 찜 버튼이 동작할 것 => '찜'에 필요한 데이터는 내 정보, 물건 정보
서버쪽 코드
app . post ( '/heart' , async ( 요청 , 응답 ) => {
if ( await db . collection ( 'heart' ). findOne ({ productid : 요청 . body . productid , userid : 요청 . user . _id }) != null ){ // heart 데이터에서 동일한 유저와 글이 있다면
await db . collection ( 'heart' ). deleteOne ({ // 그거 찾아서 지움
productid : 요청 . body . productid ,
userid : 요청 . user . _id
})
}
else { // 없으면
await db . collection ( 'heart' ). insertOne ({ // 하나 추가해줌
productid : 요청 . body . productid ,
userid : 요청 . user . _id
})
}
})
이렇게 서버는 잘 동작함(데이터가 있으면 지워지고 없으면 생김).
이제 사용자에게 보이기 위해 클라이언트 코드를 작성해야 함.
1. 사이트에 접속하면 유저의 하트 데이터에 있는 글의 하트는 채워진 채로 보여야 함.
2. 이후에 하트를 클릭 할 때마다 하트가 채워졌다 빠졌다 하는 게 바로 보여야 함(이벤트리스너).
사이트에 처음 접속했을 때 유저의 하트 데이터에 따라 채워지거나 비워져야 하므로 처음에 받아야 하는 데이터에 유저의 하트 데이터를 추가한다.
클라이언트 코드
< % if(firstdata.user==undefined){ %>
< % firstdata.products.forEach(function(a,i){ %>
< div class = "products_container nouser" data-productid = " < %=firstdata.products[i]._id%>" >
< img class = "products_img" src = < % = firstdata.products[i].img% > >
< p class = "products_title" > < %=firstdata.products[i].title%> </ p >
< div class = "products_boxing" >
< p class = "products_price" > < %=firstdata.products[i].price%> </ p >
< img class = "heart_fake" src = "./img/heart-empty.png" alt = "" >
</ div >
</ div >
< % }) %>
< % } %>
< % if(firstdata.user!=undefined){ %>
< % firstdata.products.forEach(function(a,i){ %>
< div class = "products_container" data-productid = " < %=firstdata.products[i]._id%>" >
< img class = "products_img" src = < % = firstdata.products[i].img% > >
< p class = "products_title" > < %=firstdata.products[i].title%> </ p >
< div class = "products_boxing" >
< p class = "products_price" > < %=firstdata.products[i].price%> </ p >
< % if (JSON.stringify(firstdata.heart).includes(JSON.stringify(firstdata.products[i]._id))){ %>
< img class = "heart" src = "./img/heart-full.png" alt = "" >
< % } %>
< % if (JSON.stringify(firstdata.heart).includes(JSON.stringify(firstdata.products[i]._id))==false){ %>
< img class = "heart" src = "./img/heart-empty.png" alt = "" >
< % } %>
</ div >
</ div >
< % }) %>
< % } %>
$ ( '.products_container' ). click ( function ( e ){ // 상품 하나 눌럿을 때
if ( e . target . className == 'heart' ){ // 기능하는 하트 클릭하면 실행
$ . post ( '/heart' ,{
productid : $ ( this ). data ( "productid" )
})
. done ( function ( data ){
if ( data == '지움' ){
$ ( e . target ). attr ( 'src' , './img/heart-empty.png' ); // 하트 지움
}
if ( data == '채움' ){
$ ( e . target ). attr ( 'src' , './img/heart-full.png' ); // 하트 채움
}
})
}
else if ( e . target . className == 'heart_fake' ){ // 가짜 하트 누르면
$ ( '.login_bg' ). eq ( 0 ). css ( 'display' , 'block' )
$ ( '.login_container' ). eq ( 0 ). css ( 'display' , 'block' ) // 최초의 로그인 창이 뜨게 하는 코드
}
else {
$ ( '.product_bg' ). css ( 'display' , 'block' )
}
})
$ ( '#login_submit' ). click ( function (){ // 로그인 버튼을 눌렀을 때
if ( $ ( '#login_id' ). val ()!= '' && $ ( '#login_pw' ). val ()!= '' ){ // 둘다 공백이 아니면
$ . post ( '/login' ,{ // login으로 post 요청 보냄
username : $ ( '#login_id' ). val (),
password : $ ( '#login_pw' ). val ()
})
. done ( function ( data ){
alert ( data ) // 위 코드에서 응답한 json 데이터를 알림창으로 보내줌
$ ( '.login_bg' ). css ( 'display' , 'none' )
$ ( '.login_container' ). css ( 'display' , 'none' )
if ( data == '나다장터에 오신 걸 환영합니다.' ){ // 로그인되면 바로 변할 것!!!!!
location . reload ();
}
})
}
else { alert ( '아이디 혹은 비밀번호를 입력하지 않았습니다.' )} // 둘 중 하나라도 공백이면 이거 띄워줌
})
서버쪽 코드
app . post ( '/heart' , async ( 요청 , 응답 ) => {
if ( await db . collection ( 'heart' ). findOne ({ productid : new ObjectId ( 요청 . body . productid ), userid : 요청 . user . _id }) != null ){ // heart 데이터에서 동일한 유저와 글이 있다면
await db . collection ( 'heart' ). deleteOne ({ // 그거 찾아서 지움
productid : new ObjectId ( 요청 . body . productid ),
userid : 요청 . user . _id
})
응답 . json ( '지움' )
}
else { // 없으면
await db . collection ( 'heart' ). insertOne ({ // 하나 추가해줌
productid : new ObjectId ( 요청 . body . productid ),
userid : 요청 . user . _id
})
응답 . json ( '채움' )
}
})
아무래도 첫페이지에서 데이터를 기반으로 하트가 보이므로 로그인을 하면 그냥 reload를 해서 새로고침을 시켜주도록 했다. 새로고침 없이 찜의 유무를 js로 깔아주는 건 불가능한 것 같다 ㅠㅡㅠ
근데 지금 생각해보니까 로그인을 하면 데이터가 되게 많이 바뀌고 심지어 유저의 데이터를 토대로 만들어지는 것도 많은데 전부 js로 만들어주는 건 확실히 무리인 것 같다... 로그인할 때랑 로그인 안할 때가 별로 차이가 없어보여서 욕심을 냈는데 아쉽다... 그렇지만 재밌었당
이렇게 된 김에! ejs 파일 말고 html로 다시 바꾸도록 하겠다. ssr은 다음 프로젝트에서 많이 구현할테니 이번엔 csr 위주로 하는 게 좋을 것 같다. 그리고 로그인하기 전 데이터를 뿌리고, 로그인을 하면 데이터를 또다시 가져와야서 뿌려야만 한다면 굳이 ssr로 할 필요가 없음
첫페이지도 html로 바꿔보기
< % if(firstdata.user==undefined){ %>
< div class = "chatroombtn_fake loginbtn nouser" > 채팅 </ div >
< div class = "loginbtn nouser" > 로그인 / 회원가입 </ div >
< div class = "myshopbtn_fake loginbtn nouser" > 내 상점 </ div >
< div class = "insertbtn_fake loginbtn nouser" > 내 물건 팔기 </ div >
< % } %>
< % if(firstdata.user!=undefined){ %>
< div class = "chatroombtn" > 채팅 </ div >
< div class = "logoutbtn" > 로그아웃 </ div >
< div class = "myshopbtn" > 내 상점 </ div >
< div class = "insertbtn" > 내 물건 팔기 </ div >
< % } %>
요런 ejs 성향이 짙은 친구들을 아래로(js코드로) 내려주겠다 ㅎㅎ
$ . get ( '/firstdata' )
. done ( function ( firstdata ){
if ( firstdata . user == undefined ){
$ ( '.title_btns' ). html ( `
<div class="chatroombtn_fake loginbtn nouser">채팅</div>
<div class="loginbtn nouser">로그인 / 회원가입</div>
<div class="myshopbtn_fake loginbtn nouser">내 상점</div>
<div class="insertbtn_fake loginbtn nouser">내 물건 팔기</div>` )
$ ( '.title_text' ). html ( `
<p class="newtitle _fake nouser">로그인하시면 더 다양한 기능을 이용하실 수 있습니다.</p>
` )
firstdata . products . forEach ( function ( a , i ){
$ ( '.products' ). append ( `
<div class="products_container nouser">
<img class="products_img" src= ${ firstdata . products [ i ]. img } >
<p class="products_title"> ${ firstdata . products [ i ]. title } </p>
<div class="products_boxing">
<p class="products_price"> ${ firstdata . products [ i ]. price } </p>
<img class="heart_fake" src="./img/heart-empty.png" alt="">
</div>
</div>
` )
})
}
if ( firstdata . user != undefined ){
$ ( '.title_btns' ). html ( `
<div class="chatroombtn">채팅</div>
<div class="logoutbtn">로그아웃</div>
<div class="myshopbtn">내 상점</div>
<div class="insertbtn">내 물건 팔기</div>` )
$ ( '.title_text' ). html ( `
<p class="newtitle"> ${ firstdata . user . username } 님 환영합니다.</p>
` )
이렇게 작성해주었다.
그리고 버튼을
<div class="myshopbtn_fake loginbtn nouser">내 상점</div>
이런 식으로 작성햇었는데 이젠 굳이 그럴 필요가 없게 되었다.
처음 접속할 때 로그인을 안했으면 화면1, 로그인을 했으면 화면2를 보여주는데다 로그인을 하게 되면 다시 접속하게 되니까 두 화면이 공존할 수가 없기 때문이다.
그래서 가독성을 위해 단순하게 버튼들을 바꾸어주겠다.
css 파일도 대폭 삭제해야겠다...
$ . get ( '/firstdata' )
. done ( function ( firstdata ){ // 처음 데이터 받아오기
//////////////////////////////////// 로그인 한 유저 화면 //////////////////////////////////
if ( firstdata . user == undefined ){
$ ( '.title_btns' ). html ( `
<div class="margin_top loginbtn">채팅</div>
<div class="loginbtn">로그인 / 회원가입</div>
<div class="loginbtn">내 상점</div>
<div class="loginbtn">내 물건 팔기</div>` )
firstdata . products . forEach ( function ( a , i ){
$ ( '.products' ). append ( `
<div class="products_container">
<img class="products_img" src= ${ firstdata . products [ i ]. img } >
<p class="products_title"> ${ firstdata . products [ i ]. title } </p>
<div class="products_boxing">
<p class="products_price"> ${ firstdata . products [ i ]. price } </p>
<img class="heart_fake" src="./img/heart-empty.png" alt="">
</div>
</div>
` )
})
}
//////////////////////////////////// 로그인 한 유저 화면 //////////////////////////////////
if ( firstdata . user != undefined ){
$ ( '.title_btns' ). html ( `
<div class="chatroombtn margin_top">채팅</div>
<div class="logoutbtn margin_top">로그아웃</div>
<div class="myshopbtn">내 상점</div>
<div class="insertbtn">내 물건 팔기</div>` )
$ ( '.title_text' ). html ( `
${ firstdata . user . username } 님 환영합니다.
` )
firstdata . products . forEach ( function ( a , i ){
if ( JSON . stringify ( firstdata . heart ). includes ( JSON . stringify ( firstdata . products [ i ]. _id ))){
$ ( '.products' ). append ( `
<div class="products_container" data-productid=" ${ firstdata . products [ i ]. _id } ">
<img class="products_img" src= ${ firstdata . products [ i ]. img } >
<p class="products_title"> ${ firstdata . products [ i ]. title } </p>
<div class="products_boxing">
<p class="products_price"> ${ firstdata . products [ i ]. price } </p>
<img class="heart" src="./img/heart-full.png" alt="">
</div>
</div>
` )
}
else if ( JSON . stringify ( firstdata . heart ). includes ( JSON . stringify ( firstdata . products [ i ]. _id ))== false ){
$ ( '.products' ). append ( `
<div class="products_container" data-productid=" ${ firstdata . products [ i ]. _id } ">
<img class="products_img" src= ${ firstdata . products [ i ]. img } >
<p class="products_title"> ${ firstdata . products [ i ]. title } </p>
<div class="products_boxing">
<p class="products_price"> ${ firstdata . products [ i ]. price } </p>
<img class="heart" src="./img/heart-empty.png" alt="">
</div>
</div>
` )
}
})
$ ( '.chatroombtn' ). click ( function (){
$ . get ( '/chatroom' )
. done ( function ( data ){ // 로그인 된 상태
console . log ( data )
})
. fail ( function ( data ){ // 로그인 안된 상태
console . log ( data )
})
})
}
//////////////////////////////////// 공통 화면(기능) //////////////////////////////////
$ ( '.products_container' ). click ( function ( e ){ // 상품 하나 눌럿을 때
if ( e . target . className == 'heart' ){ // 기능하는 하트 클릭하면 실행
$ . post ( '/heart' ,{
productid : $ ( this ). data ( "productid" )
})
. done ( function ( data ){
if ( data == '지움' ){
$ ( e . target ). attr ( 'src' , './img/heart-empty.png' ); // 하트 지움
}
if ( data == '채움' ){
$ ( e . target ). attr ( 'src' , './img/heart-full.png' ); // 하트 채움
}
})
}
else if ( e . target . className == 'heart_fake' ){ // 가짜 하트 누르면
$ ( '.login_bg' ). eq ( 0 ). css ( 'display' , 'block' )
$ ( '.login_container' ). eq ( 0 ). css ( 'display' , 'block' ) // 최초의 로그인 창이 뜨게 하는 코드
}
else {
$ ( '.product_bg' ). css ( 'display' , 'block' )
}
})
$ ( '.product_close' ). click ( function (){
$ ( '.product_bg' ). css ( 'display' , 'none' )
})
$ ( '.insertbtn' ). click ( function (){ // 물건 올리기 버튼 누르면
$ ( '.insert_bg' ). css ( 'display' , 'block' )
})
$ ( '.insert_close' ). click ( function (){ // 물건 올리기 x 누르면
$ ( '.insert_bg' ). css ( 'display' , 'none' )
})
$ ( '#insert_price' ). on ( 'keyup' , function () {
$ ( this ). val ( $ ( this ). val (). replace ( / [^ 0-9 ] / g , '' ));
});
$ ( '#insert_submit' ). click ( function (){ // 물건 제출 버튼 누르면
if ( $ ( '#insert_title' ). val ()!= '' && $ ( '#insert_text' ). val ()!= '' && $ ( '#insert_price' ). val ()!= '' && $ ( '#insert_img' ). val ()!= '' ){ // 셋다 공백이 아니면
alert ( '판매글을 게시했습니다.' )
}
else { alert ( '전부 입력해야 판매글을 등록할 수 있습니다.' )} // 둘 중 하나라도 공백이면 이거 띄워줌
})
$ ( '#login_submit' ). click ( function (){ // 로그인 버튼을 눌렀을 때
if ( $ ( '#login_id' ). val ()!= '' && $ ( '#login_pw' ). val ()!= '' ){ // 둘다 공백이 아니면
$ . post ( '/login' ,{ // login으로 post 요청 보냄
username : $ ( '#login_id' ). val (),
password : $ ( '#login_pw' ). val ()
})
. done ( function ( data ){
alert ( data ) // 위 코드에서 응답한 json 데이터를 알림창으로 보내줌
$ ( '.login_bg' ). css ( 'display' , 'none' )
$ ( '.login_container' ). css ( 'display' , 'none' )
if ( data == '나다장터에 오신 걸 환영합니다.' ){ // 로그인되면 바로 변할 것!!!!!
// $('').css('display','none') // 유저가 아닐 때만 보였던 거 안 보이게
// $('.hide').css('display','block')
// $('.title_text').html(`${$('#login_id').val()}님 환영합니다.`) // 로그인하면 환영 제목을 바로 바꿔줌
location . reload ();
}
})
}
else { alert ( '아이디 혹은 비밀번호를 입력하지 않았습니다.' )} // 둘 중 하나라도 공백이면 이거 띄워줌
})
대충 이정도 수정해줬다.
로그인 후 변화해야 하는 것(5)
5. 내 상점 버튼이 동작할 것 => '내상점'에 필요한 데이터는 내 정보, 내가 올린 물건들, 내가 찜한 물건들
<이건 마지막에 하는 게 편할 듯>
로그인 후 변화해야 하는 것(6) <= new!
6. 로그인을 하면 '로그인하시면 다양한 기능을 이용하실 수 있습니다.'가 'username님 환영합니다.' 로 바뀔 것(카테고리는 삭제).
로그인하지 않았을 때의 화면
로그인 했을 때의 화면
-클라이언트 코드-
< % if(firstdata.user==undefined){ %>
< p class = "newtitle _fake nouser" > 로그인하시면 다양한 기능을 이용하실 수 있습니다. </ p >
< % } %>
< p class = "newtitle hide" > //로그인을 하고 나면 이게 내용이 바뀌어서 보임 </ p >
< % if(firstdata.user!=undefined){ %>
< p class = "newtitle" > < %= firstdata.user.username %>님 환영합니다. </ p >
< % } %>
(js)
$ ( '#login_submit' ). click ( function (){ // 로그인 버튼을 눌렀을 때
if ( $ ( '#login_id' ). val ()!= '' && $ ( '#login_pw' ). val ()!= '' ){ // 둘다 공백이 아니면
$ . post ( '/login' ,{ // login으로 post 요청 보냄
username : $ ( '#login_id' ). val (),
password : $ ( '#login_pw' ). val ()
})
. done ( function ( data ){
alert ( data ) // 위 코드에서 응답한 json 데이터를 알림창으로 보내줌
$ ( '.login_bg' ). css ( 'display' , 'none' )
$ ( '.login_container' ). css ( 'display' , 'none' )
if ( data == '나다장터에 오신 걸 환영합니다.' ){ // 로그인되면 바로 변할 것!!!!!
$ ( '.nouser' ). css ( 'display' , 'none' ) // 유저가 아닐 때만 보였던 거 안 보이게
$ ( '.hide' ). css ( 'display' , 'block' )
$ ( '.newtitle' ). html ( ` ${ $ ( '#login_id' ). val () } 님 환영합니다.` ) // 로그인하면 환영 제목을 바로 바꿔줌
}
})
}
else { alert ( '아이디 혹은 비밀번호를 입력하지 않았습니다.' )} // 둘 중 하나라도 공백이면 이거 띄워줌
})
서버쪽 코드는 딱히 수정된 게 없음.
추가 문제 해결
하트를 누르면 $('products_container ').click 이벤트가 동시 발생함.
$ ( '.products_container' ). click ( function ( e ){
if ( e . target . className == 'heart' ){
alert ( '찜 기능 동작' )
}
else {
$ ( '.product_bg' ). css ( 'display' , 'block' )
}
})
그래서 하트의 이벤트리스너를 삭제하고, 상품 컨테이너의 이벤트리스너를 이렇게 수정함.
오늘 하루 알게 된 작은 꿀팁 :
- 클래스명 가져오는 법 -
console . log ( e . target . className ) <= 클릭한 요소의 클래스명을 콘솔창에 출력
console . log ( $ ( '#heart' ). attr ( "class" )) <= heart 아이디를 가진 것의 클래스명을 콘솔창에 출력
유용하게 쓰일 것 같다.
heartlist . forEach ( function ( a , i ){
await db . collection ( 'heart' ). findOne ({ userid : 요청 . user . _id })
if ( firstdata . heart [ i ]. productid == firstdata . products [ i ]. _id ){
}
})
이렇게 배열 반복문 안에서는 await가 먹히지 않는다. 대신 for문을 사용하면 된다.
- 오브젝트 아이디 비교하는 법 -
firstdata . heart [ 1 ] <= new ObjectId("661eb9eb2447adef46ec7bbf")
firstdata . products [ 2 ]. _id <= new ObjectId("661eb9eb2447adef46ec7bbf")
위의 것들은 똑같다.
콘솔을 해보면 똑같이 생겼다.
근데 이렇게 비교하면 다르다고 한다.
심지어 타입오프로 형식값을 출력해봐도 둘다 똑같은 오브젝트다.
아마도 하나는 내가 만들어낸 오브젝트 아이디이고, 하나는 자동으로 만들어진 오브젝트 아이디라서 그런 것 같다.
이럴때는
if ( firstdata . heart [ 1 ]. equals ( firstdata . products [ 2 ]. _id )){
console . log ( '같음' )
}
else {
console . log ( '다름' )
}
이렇게 비교하면 '같음'이 출력된다. 굿
'==' 연산자는 객체의 주소를 비교하여 일치하는지 확인하고, 'equals()' 메서드는 객체의 내용을 비교하여 같은지 확인하는 메서드라서 그렇다고 한다.
https://milku.tistory.com/112 , https://inpa.tistory.com/entry/JS-%F0%9F%9A%80-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B0%B0%EC%97%B4-%EA%B5%AC%EC%84%B1-%EB%B9%84%EA%B5%90%ED%95%98%EA%B8%B0
🚀 자바스크립트 배열 동등 비교 방법
자바스크립트 배열 동등 비교 숫자나 문자열 비교할때는 == 이나 === 연산자를 이용하면 되지만, 배열이나 객체를 비교할때는 불가능하다. 왜냐하면 reference 타입으로써 값이 비교 되는 것이 아닌
inpa.tistory.com
[Java] 문자열(String) 비교 방법 ==, equals() 차이
서비스에서 문자열을 비교하던 중 같은 값을 비교했는데 false가 나오는 상황이 발생했습니다. 원인을 찾아보니 == 연산자로 문자열을 비교했기 때문이었습니다. == 연산자는 객체의 주소를 비교
milku.tistory.com
- 포함되는지 확인하는 법 -
console . log ( firstdata . heart . includes ( firstdata . products [ 2 ]. _id ))
firstdata.heart = [new ObjectId("661eb55f8e91f94bcbcca2ba"), new ObjectId("661eb9eb2447adef46ec7bbf")]
firstdata.products[2]._id = new ObjectId("661eb9eb2447adef46ec7bbf")
이면 포함하는 게 맞을텐데 콘솔창 결과는 false 였다.
console . log ( JSON . stringify ( firstdata . heart ). includes ( JSON . stringify ( firstdata . products [ 2 ]. _id )))
혹시나 해서 제이슨 문자열로 변환하고 비교해봤더니 true가 떴다.
두 개의 형식이 조금 다른 것 같다.
위에서도 그렇고 이 부분은 좀 더 공부해봐야겠다.
오늘의 감상 : 코드를 짜면서 생각했는데, 난 아직 처음부터 모든 것을 기획할 능력은 없는 것 같다. 처음에 기획을 하려고 하면 뭘 기획해야 할지 잘 모르겠는데 직접 디자인하고 눈으로 보면서 만들어가야 이런 저런 걸 '고쳐야'겠다는 생각이 든다. 그래서 이렇게 직접 코드를 짜고 분석하다보면 점점 살이 붙어간다. 근데 결국 완성도 높게 만드는 내가 대단하다고 느낀다. ㅎㅎ 이렇게 여러번 하면서 점점 깨닫다보면 나중엔 처음 기획 한번만으로 줄줄이 만들 수 있는 날이 오지 않을까?