.png)
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.jsconst 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);
}
FeedServicepublic int feedFavProc(FeedFavEntity param, int type) {
param.setIuser(auth.getLoginUserPk());
if(type == 1) {
return mapper.insFeedFav(param);
}
return mapper.delFeedFav(param);
}
FeedMapperint 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 pageint limitint iuserForMyFeedint iuserForFavint getStarIdx() {return (page-1) * limit}
FeedController@ResponseBody
@GetMapping("list2")
public List<FeedDomain2> selFeedList2(FeedDTO param) {
return service.selFeedList2(param);
}
FeedServicepublic List<FeedDomain2> selFeedList2(FeedDTO param) {
param.setIuserForFav(auth.getLoginUserPk());
return mapper.selFeedList2(param);
}
FeedDomain2
int iFeedString locationString ctntint iuserString regdtString writerString mainProfileint favCntint isFavList<FeedImgEntity> imgListFeedCmtDomain 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>