const param = { ...globalConstElem.dataset };
param
์ dataset
๋ฐฐ์ด๊ฐ์ด ๋ด๊ฒจ์ ๊ฐ์ฒด๊ฐ ๋๋ค.html
์์ feed
๋ฅผ ์๋ง๋ค๊ณ js
์์ ๋ง๋ ์ด์ ๋home.html
์ ๋ค์ด๊ฐ์ ๋ง์ ํผ๋๋ค์ด ๋จ๊ฒ ํ๊ธฐ ์ํด์๋คinfinite scroller
๋ฅผ ์ฐ๊ธฐ ์ํ ๊ฒ์ด๋คhome.html
์ ๋ค์ด๊ฐ์ ๋ง์ ํผ๋๋ค์ด ๋จ๋ ์ด์ js
์ ajax
ํจ์๋ฅผ ์คํ์ํค๊ธฐ ๋๋ฌธhome2.js
feedObj.url = '/feed/list2';
feedObj.setScrollInfinity(window);
feedObj.getFeedList(1);
feed.js
const feedObj = {
limit: 5,
itemLength: 0,
currentPage: 1,
url: '',
iuser: 0,
swiper: null,
containerElem: document.querySelector('#feedContainer'),
loadingElem: document.querySelector('.loading'),
containerElem: document.querySelector('#feedContainer')
:<div id="feedContainer"></div>
loadingElem: document.querySelector('.loading')
:<div class="loading hide"><img th:src="@{/img/loading.gif}"></div>
makeFeedList: function(data) {
if(data.length == 0) { return; }
for(let i=0; i<data.length; i++) {
const item = data[i];
const itemContainer = document.createElement('div');
itemContainer.classList.add('item');
// ๊ธ์ด์ด ์ ๋ณด ์์ญ
let imgTag = ``;
if(item.mainProfile != null) {
imgTag = `<img src="/pic/profile/${item.iuser}/${item.mainProfile}" class="pointer profile wh30"
token interpolation">${item.iuser});">`;
}
const regDtInfo = getDateTimeInfo(item.regdt);
const topDiv = document.createElement('div');
topDiv.classList.add('top')
topDiv.innerHTML = `
<div class="itemProfileCont">${imgTag}</div>
<div>
<div><span class="pointer"token interpolation">${item.iuser});">${item.writer}</span> - ${regDtInfo}</div>
<div>${item.location == null ? '' : item.location}</div>
</div>
`;
//์ด๋ฏธ์ง์์ญ
const imgDiv = document.createElement('div');
imgDiv.classList.add('itemImg');
const swiperContainerDiv = document.createElement('div');
swiperContainerDiv.classList.add('swiper-container');
const swiperWrapperDiv = document.createElement('div');
swiperWrapperDiv.classList.add('swiper-wrapper');
swiperContainerDiv.append(swiperWrapperDiv);
imgDiv.append(swiperContainerDiv);
for(let z=0; z<item.imgList.length; z++) {
const imgObj = item.imgList[z];
const swiperSlideDiv = document.createElement('div');
swiperSlideDiv.classList.add('swiper-slide');
const img = document.createElement('img');
img.src = `/pic/feed/${item.ifeed}/${imgObj.img}`;
swiperSlideDiv.append(img);
swiperWrapperDiv.append(swiperSlideDiv);
}
itemContainer.append(topDiv);
itemContainer.append(imgDiv);
const itemContainer = document.createElement('div'); itemContainer.classList.add('item');
<div class='item'></div>
const topDiv = document.createElement('div');
topDiv.classList.add('top')
topDiv.innerHTML = `
<div class="itemProfileCont">${imgTag}</div>
<div>
<div><span class="pointer"token interpolation">${item.iuser});">${item.writer}</span> - ${regDtInfo}</div>
<div>${item.location == null ? '' : item.location}</div>
</div>
`;
[ topDiv ]
<div class='top'>
<div class="itemProfileCont">
<img src="/pic/profile/${item.iuser}/${item.mainProfile}" class="pointer profile wh30"
onclick="moveToProfile(${item.iuser});" onerror="this.style.display='none';">
</div>
<div>
<div>
<span class="pointer" onclick="moveToProfile(${item.iuser});">${item.writer}</span> - ${regDtInfo}
</div>
<div>${item.location == null ? '' : item.location}</div>
</div>
</div>
const imgDiv = document.createElement('div'); imgDiv.classList.add('itemImg');
:<div class="itemImg"></div>
const swiperContainerDiv = document.createElement('div'); swiperContainerDiv.classList.add('swiper-container');
:<div class="swiper-container"></div>
const swiperWrapperDiv = document.createElement('div'); swiperWrapperDiv.classList.add('swiper-wrapper');
:<div class="swiper-wrapper"></div>
const swiperSlideDiv = document.createElement('div'); swiperSlideDiv.classList.add('swiper-slide'); const img = document.createElement('img'); img.src = `/pic/feed/${item.ifeed}/${imgObj.img}`; swiperSlideDiv.append(img); swiperWrapperDiv.append(swiperSlideDiv);
[ imgDiv ]
<div class="itemImg">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img src="/pic/feed/${item.ifeed}/${imgObj.img}">
</div>
</div>
</div>
</div>
itemContainer.append(topDiv); itemContainer.append(imgDiv);
<div class='item'> * ์ฌ๋ฌ๋ฒ ๋ฐ๋ณต
<div class='top'>
<div class="itemProfileCont">
<img src="/pic/profile/${item.iuser}/${item.mainProfile}" class="pointer profile wh30"
onclick="moveToProfile(${item.iuser});" onerror="this.style.display='none';">
</div>
<div>
<div>
<span class="pointer" onclick="moveToProfile(${item.iuser});">${item.writer}</span> - ${regDtInfo}
</div>
<div>${item.location == null ? '' : item.location}</div>
</div>
</div>
<div class="itemImg">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide"> * ์ฌ๋ฌ๋ฒ ๋ฐ๋ณต
<img src="/pic/feed/${item.ifeed}/${imgObj.img}">
</div>
</div>
</div>
</div>
</div>
feed.js
//์ข์์ ์์ญ
const favDiv = document.createElement('div');
favDiv.classList.add('favCont');
const heartIcon = document.createElement('i');
heartIcon.className = 'fa-heart pointer';
if(item.isFav === 1) { //์ข์์ O
heartIcon.classList.add('fas');
} else { //์ข์์ X
heartIcon.classList.add('far');
}
const heartCntSpan = document.createElement('span');
heartCntSpan.innerText = item.favCnt;
heartIcon.addEventListener('click', ()=> {
item.isFav = 1 - item.isFav;
fetch(`fav?ifeed=${item.ifeed}&type=${item.isFav}`)
.then(res => res.json())
.then(myJson => {
if(myJson === 1) {
switch (item.isFav) {
case 0: //O > X
heartIcon.classList.remove('fas');
heartIcon.classList.add('far');
heartCntSpan.innerText--;
break;
case 1: //X > O
heartIcon.classList.remove('far');
heartIcon.classList.add('fas');
heartCntSpan.innerText++;
break;
}
}
});
});
favDiv.append(heartIcon);
favDiv.append(heartCntSpan);
itemContainer.append(favDiv);
const favDiv = document.createElement('div'); favDiv.classList.add('favCont');
:<div class="favCont"></div>
const heartIcon = document.createElement('i'); heartIcon.className = 'fa-heart pointer';
:<i class="fa-heart pointer"></i>
const heartCntSpan = document.createElement('span'); heartCntSpan.innerText = item.favCnt;
:<span>${item.favCnt}</span>
favDiv.append(heartIcon); favDiv.append(heartCntSpan);
<div class="favCont"> <i class="fa-heart pointer"></i> <span>${item.favCnt}</span> </div>
itemContainer.append(favDiv);
<div class='item'> * ์ฌ๋ฌ๋ฒ ๋ฐ๋ณต //๊ธ์ด์ด ์ ๋ณด ์์ญ <div class='top'> <div class="itemProfileCont"> <img src="/pic/profile/${item.iuser}/${item.mainProfile}" class="pointer profile wh30" onclick="moveToProfile(${item.iuser});" onerror="this.style.display='none';"> </div> <div> <div> <span class="pointer" onclick="moveToProfile(${item.iuser});">${item.writer}</span> - ${regDtInfo} </div> <div>${item.location == null ? '' : item.location}</div> </div> </div> //์ด๋ฏธ์ง ์์ญ <div class="itemImg"> <div class="swiper-container"> <div class="swiper-wrapper"> <div class="swiper-slide"> * ์ฌ๋ฌ๋ฒ ๋ฐ๋ณต <img src="/pic/feed/${item.ifeed}/${imgObj.img}"> </div> </div> </div> </div> //์ข์์ ์์ญ <div class="favCont"> <i class="fa-heart pointer"></i> ๐ click ๐จ fav?ifeed=${item.ifeed}&type=${item.isFav} <span>${item.favCnt}</span> </div> </div>
FeedController
@ResponseBody
@GetMapping("/fav")
public int feedFavProc(FeedFavEntity param, int type) { //type: 1 - ins(๋ฑ๋ก), 0 - del(์ทจ์)
System.out.println(param);
System.out.println("type: " + type);
return service.feedFavProc(param, type);
}
FeedService
public int feedFavProc(FeedFavEntity param, int type) {
param.setIuser(auth.getLoginUserPk());
if(type == 1) {
return mapper.insFeedFav(param);
}
return mapper.delFeedFav(param);
}
FeedMapper
int insFeedFav(FeedFavEntity param);
int delFeedFav(FeedFavEntity param);
<insert id="insFeedFav">
INSERT INTO t_feed_fav
( ifeed, iuser )
VALUES
( #{ifeed}, #{iuser} )
</insert>
<delete id="delFeedFav">
DELETE FROM t_feed_fav
WHERE ifeed = #{ifeed}
AND iuser = #{iuser}
</delete>
feed.js
if(item.ctnt != null) { // ๊ธ๋ด์ฉ ์์ญ
const ctntDiv = document.createElement('div');
ctntDiv.innerText = item.ctnt;
ctntDiv.classList.add('itemCtnt');
itemContainer.append(ctntDiv);
}
//๋๊ธ ์์ญ
const cmtDiv = document.createElement('div');
const cmtListDiv = document.createElement('div');
const cmtFormDiv = document.createElement('div');
cmtDiv.append(cmtListDiv);
if(item.cmt != null && item.cmt.isMore === 1) {
const moreCmtDiv = document.createElement('div');
const moreCmtSpan = document.createElement('span');
moreCmtSpan.className = 'pointer';
moreCmtSpan.innerText = '๋๊ธ ๋๋ณด๊ธฐ';
moreCmtSpan.addEventListener('click', () => {
moreCmtSpan.remove();
fetch(`cmt?ifeed=${item.ifeed}`)
.then(res => res.json())
.then(result => {
result.forEach(obj => {
const cmtItemContainerDiv = this.makeCmtItem(obj);
cmtListDiv.append(cmtItemContainerDiv);
})
});
});
moreCmtDiv.append(moreCmtSpan);
cmtDiv.append(moreCmtDiv);
}
cmtDiv.append(cmtFormDiv);
const cmtInput = document.createElement('input');
cmtInput.type = 'text';
cmtInput.placeholder = '๋๊ธ์ ์
๋ ฅํ์ธ์...';
cmtInput.addEventListener('keyup', (e) => {
if(e.key === 'Enter') {
cmtBtn.click();
}
});
if(item.cmt != null) { //๋๊ธ ์์
const cmtItemContainerDiv = this.makeCmtItem(item.cmt);
cmtListDiv.append(cmtItemContainerDiv);
}
const cmtBtn = document.createElement('input');
cmtBtn.type = 'button';
cmtBtn.value = '๋ฑ๋ก';
cmtBtn.addEventListener('click', () => {
const cmt = cmtInput.value;
if(cmt.length === 0) {
alert('๋๊ธ ๋ด์ฉ์ ์์ฑํด ์ฃผ์ธ์.');
return;
}
const param = {
ifeed: item.ifeed,
cmt: cmt
}
fetch('cmt', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(param)
})
.then(res => res.json())
.then(myJson => {
console.log(myJson);
switch(myJson) {
case 0:
alert('๋๊ธ์ ๋ฑ๋กํ ์ ์์ต๋๋ค.');
break;
case 1:
//๋๊ธ ์ถ๊ฐํ๋ค.
const globalConstElem = document.querySelector('#globalConst');
const param = { ...globalConstElem.dataset };
param.cmt = cmtInput.value;
const cmtItemDiv = this.makeCmtItem(param);
cmtListDiv.append(cmtItemDiv);
cmtInput.value = '';
break;
}
})
.catch(err => {
console.log(err);
});
});
cmtFormDiv.append(cmtInput);
cmtFormDiv.append(cmtBtn);
itemContainer.append(cmtDiv);
this.containerElem.append(itemContainer);
}
if(this.swiper != null) { this.swiper = null; }
this.swiper = new Swiper('.swiper-container', {
direction: 'horizontal',
loop: false,
});
},
makeCmtItem(obj)
makeCmtItem: function({iuser, writerProfile, writer, cmt}) {
const cmtItemContainerDiv = document.createElement('div');
cmtItemContainerDiv.className = 'cmtItemCont';
//ํ๋กํ
const cmtItemProfileDiv = document.createElement('div');
cmtItemProfileDiv.className = 'cmtItemProfile';
const cmtItemWriterProfileImg = document.createElement('img');
cmtItemWriterProfileImg.src = `/pic/profile/${iuser}/${writerProfile}`;
cmtItemWriterProfileImg.className = 'profile wh30 pointer';
cmtItemWriterProfileImg.addEventListener('click', () => {
moveToProfile(iuser);
});
cmtItemProfileDiv.append(cmtItemWriterProfileImg);
cmtItemContainerDiv.append(cmtItemProfileDiv);
//๋๊ธ
const cmtItemCtntDiv = document.createElement('div');
cmtItemCtntDiv.className = 'cmtItemCtnt';
cmtItemCtntDiv.innerHTML = `<div class="pointer"token interpolation">${iuser});">${writer}</div><div>${cmt}</div>`;
cmtItemContainerDiv.append(cmtItemCtntDiv);
return cmtItemContainerDiv;
}
<div class='item'> * ์ฌ๋ฌ๋ฒ ๋ฐ๋ณต
//๊ธ์ด์ด ์ ๋ณด ์์ญ
<div class='top'>
<div class="itemProfileCont">
<img src="/pic/profile/${item.iuser}/${item.mainProfile}" class="pointer profile wh30"
onclick="moveToProfile(${item.iuser});" onerror="this.style.display='none';">
</div>
<div>
<div>
<span class="pointer" onclick="moveToProfile(${item.iuser});">${item.writer}</span> - ${regDtInfo}
</div>
<div>${item.location == null ? '' : item.location}</div>
</div>
</div>
//์ด๋ฏธ์ง ์์ญ
<div class="itemImg">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide"> * ์ฌ๋ฌ๋ฒ ๋ฐ๋ณต
<img src="/pic/feed/${item.ifeed}/${imgObj.img}">
</div>
</div>
</div>
</div>
//์ข์์ ์์ญ
<div class="favCont">
<i class="fa-heart pointer"></i> ๐ click ๐ fav?ifeed=${item.ifeed}&type=${item.isFav}
<span>${item.favCnt}</span>
</div>
//๊ธ ๋ด์ฉ ์์ญ
<div class="itemCtnt">${item.ctnt}</div>
//๋๊ธ์์ญ
<div>
<div>
<div>
<div> ๐ cmtListDiv
<span>๋๊ธ๋๋ณด๊ธฐ</span> ๐ click ๐ remove() ๐ cmt?ifeed=${item.ifeed}
</div> ๐ makeCmtItem(obj) * ๋ฐ๋ณต
</div>
</div>
<input type="text" placeholder="๋๊ธ..." onkeyup="์ํฐโcmtBtn.click()"></input>
<input type="button" value="๋ฑ๋ก"></input> ๐ click ๐ cmt(item.ifeed, cmtInput.value)
๐ makeCmtItem({ ...globalConstElem.dataset }) ๐ cmtListDiv.append()
</div>
</div>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="basicLayout">
<th:block layout:fragment="css">
<link rel="stylesheet" href="https://unpkg.com/swiper/swiper-bundle.min.css">
<link rel="stylesheet" th:href="@{/css/feed/home.css}">
</th:block>
<th:block layout:fragment="js">
<script src="https://unpkg.com/swiper/swiper-bundle.min.js"></script>
<script defer th:src="@{/js/feed.js}"></script>
<script defer th:src="@{/js/feed/home2.js}"></script>
</th:block>
<th:block layout:fragment="content">
<div id="feedContainer"></div>
<div class="loading hide"><img th:src="@{/img/loading.gif}"></div>
</th:block>
</html>
<th:block layout:fragment="content">
<div id="feedContainer">
<div class='item'> * ์ฌ๋ฌ๋ฒ ๋ฐ๋ณต
//๊ธ์ด์ด ์ ๋ณด ์์ญ
<div class='top'>
<div class="itemProfileCont">
<img src="/pic/profile/${item.iuser}/${item.mainProfile}" class="pointer profile wh30"
onclick="moveToProfile(${item.iuser});" onerror="this.style.display='none';">
</div>
<div>
<div>
<span class="pointer" onclick="moveToProfile(${item.iuser});">${item.writer}</span> - ${regDtInfo}
</div>
<div>${item.location == null ? '' : item.location}</div>
</div>
</div>
//์ด๋ฏธ์ง ์์ญ
<div class="itemImg">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide"> * ์ฌ๋ฌ๋ฒ ๋ฐ๋ณต
<img src="/pic/feed/${item.ifeed}/${imgObj.img}">
</div>
</div>
</div>
</div>
//์ข์์ ์์ญ
<div class="favCont">
<i class="fa-heart pointer"></i>๐ click ๐ fav?ifeed=${item.ifeed}&type=${item.isFav}
<span>${item.favCnt}</span>
</div>
//๊ธ ๋ด์ฉ ์์ญ
<div class="itemCtnt">${item.ctnt}</div>
//๋๊ธ์์ญ
<div>
<div>
<div>
<div> ๐ cmtListDiv
<span>๋๊ธ๋๋ณด๊ธฐ</span> ๐ click ๐ remove() ๐ cmt?ifeed=${item.ifeed}
</div> ๐ makeCmtItem(obj) * ๋ฐ๋ณต
</div>
</div>
<input type="text" placeholder="๋๊ธ..." onkeyup="์ํฐโcmtBtn.click()"></input>
<input type="button" value="๋ฑ๋ก"></input> ๐ click ๐ cmt(item.ifeed, cmtInput.value)
๐ makeCmtItem({ ...globalConstElem.dataset }) ๐ cmtListDiv.append()
</div>
</div>
</div>
<div class="loading hide"><img th:src="@{/img/loading.gif}"></div>
</th:block>
feed.js
๊ฐ ๋์๊ฐ๋ ๊ตฌ์กฐ
- ์ฒ์
getFeedList
ํธ์ถ
โณ ์ฌ๊ธฐ์makeFeedList
ํธ์ถ- ์คํฌ๋กค (์ด ๋ถ๋ถ์ด ๊ณ์ ๋ฐ๋ณต)
โณ ์ฌ๊ธฐ์getFeedList
ํธ์ถ
โณ ์ฌ๊ธฐ์makeFeedList
ํธ์ถ
feed.js
- getFeedList
getFeedList: function(page) {
this.showLoading();
fetch(`${this.url}?iuserForMyFeed=${this.iuser}&page=${page}&limit=${this.limit}`)
.then(res => res.json())
.then(myJson => {
console.log(myJson);
this.itemLength = myJson.length;
this.makeFeedList(myJson);
}).catch(err => {
console.log(err);
}).then(() => {
this.hideLoading();
});
},
FeedDTO
int page
int limit
int iuserForMyFeed
int iuserForFav
int getStarIdx() {return (page-1) * limit}
FeedController
@ResponseBody
@GetMapping("list2")
public List<FeedDomain2> selFeedList2(FeedDTO param) {
return service.selFeedList2(param);
}
FeedService
public List<FeedDomain2> selFeedList2(FeedDTO param) {
param.setIuserForFav(auth.getLoginUserPk());
return mapper.selFeedList2(param);
}
FeedDomain2
int iFeed
String location
String ctnt
int iuser
String regdt
String writer
String mainProfile
int favCnt
int isFav
List<FeedImgEntity> imgList
FeedCmtDomain cmt
FeedMapper
List<FeedDomain2> selFeedList2(FeedDTO param);
iuserForMyFeed
: 0page
: 1limit
: 5iuserForFav
: ๋starIdx
: 0 โ 5 โ 10 โ 15...
SELECT
A.ifeed, A.location, A.ctnt, A.iuser, A.regdt
, C.nm AS writer, C.mainProfile, IFNULL(E.cnt, 0) AS favCnt
<if test="iuserForFav > 0"> ๐๐ฆtrue๐ฆ
, CASE WHEN D.ifeed IS NULL THEN 0 ELSE 1 END AS isFav
</if>
FROM t_feed A
INNER JOIN t_user C
ON A.iuser = C.iuser ๐๐ฆ์ด๋ค ์ฌ๋๋ค์ด(nm, mainProfile) ์ด ๊ธ๋ค์
ifeed, location, ctnt, iuser, regdt, ์ข์์๊ฐฏ์์ ๊ฐ๊ฐ์ ๊ธ๋ค์
๋ด๊ฐ ์ข์์ ํ๋์ง ์ฌ๋ถ isFav๊ฐ ๊ฐ๊ฐ ๋ชจ์ฌ์LIST๊ฐ ๋๋ค. ๐ฆ
<if teset="iuserForMyFeed > 0"> ๐๐ฆfalse๐ฆ
AND C.iuser = ${iuserForMyFeed} ๐๐ฆ๋ง์ฝ true๋ผ๋ฉด ๋ด๊ฐ ํ๋ก์ฐํ ์ฌ๋๋ค์ ๊ธ๋ง ๋ณผ ์ ์๋ค.๐ฆ
</if>
LEFT JOIN (
SELECT ifeed, COUNT(ifeed) AS cnt
FROM t_feed_fav
GROUP BY ifeed
) E ๐๐ฆ๊ทธ ๊ธ์ ์ข์์ ๊ฐฏ์ , ์์์ E.cnt๋ก ์ฐ์ธ๋ค๐ฆ
ON A.ifeed = E.ifeed
<if test="iuserForFav > 0"> ๐๐ฆtrue๐ฆ
LEFT JOIN t_feed_fav D
ON D.iuser = ${iuserForFav} ๐๐ฆ๊ทธ ๊ธ์ ๋๋ ์ข์์ ๋๋ ๋์ง ์ฌ๋ถ๐ฆ
AND A.ifeed = D.ifeed ๐๐ฆ์์์ D.ifeed๋ผ๊ณ ์ฐ์ธ๋ค๐ฆ
</if>
ORDER BY ifeed DESC ๐๐ฆ๋ฆฌ์คํธ๋ฅผ ์ต์ ์์ผ๋ก ๋์ดํ๋ค๐ฆ
LIMIT #{starIds} , #{limit} ๐๐ฆ0, 5 โ ๊ฐ์ฅ ์ฒ์๋ถํฐ 5๊ฐ๋ฅผ๐ฆ
<resultMap id="FeedDomainMap" type="FeedDomain2">
<result property="ifeed" column="ifeed"></result>
<association property="cmt" column="ifeed" select="selFeedCmt"></association>
<collection property="imgList" column="ifeed" select="selFeedImgList"></collection>
</resultMap>
<select id="selFeedCmt" resultType="FeedCmtDomain">
SELECT A.*, COUNT(A.icmt) - 1 AS isMore
FROM (
SELECT
A.icmt, A.cmt, A.regdt, A.ifeed
, B.iuser, B.nm as writer, B.mainProfile as writerProfile
FROM t_feed_cmt A
INNER JOIN t_user B
ON A.iuser = B.iuser
WHERE A.ifeed = ${ifeed}
ORDER BY icmt ASC
LIMIT 2
) A
GROUP BY A.ifeed
</select>
<select id="selFeedImgList" resultType="FeedImgEntity">
SELECT ifeedimg, ifeed, img FROM t_feed_img
WHERE ifeed = #{ifeed}
</select>