일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- jQuery
- DI
- programmers
- google oauth
- WIL
- 생명주기 콜백
- flask
- session
- oauth
- Stream
- Project
- Java
- 항해99
- cookie
- hanghae99
- jenkins
- JWT
- spring
- real time web
- web
- Anolog
- SseEmitter
- javascript
- Hibernate
- server send event
- bean
- JPA
- python
- Spring Security
- html
- Today
- Total
끄적끄적 코딩일지
[Spring] 이미지월드컵 만들기 (마지막) - 메인화면 월드컵 진행 및 결과 화면 본문
[Spring] 이미지월드컵 만들기 (마지막) - 메인화면 월드컵 진행 및 결과 화면
BaekGyuHyeon 2022. 6. 2. 19:11드디어 끝이 보이네.... 부트캠프 중간중간 조금씩 만들어 가니까 진도가 빠지지 않는다. 간단한 프로젝트라서 맘잡고 만들면 2~3일이면 될것같은데 생각보다 오래걸린다.
그래도 월드컵 게임 진행화면과 결과 화면까지 만들었으니 남은건 로그인, 댓글 기능만 남았다.
일단 메인화면 쪽 코드이다.
<main.html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>이미지 월드컵</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="/js/main.js" type="text/javascript"></script>
</head>
<body>
<div class="p-3 origin">
<table class="origin table">
<thread class="thread-dark">
<tr>
<th scope="col">번호</th>
<th scope="col">제목</th>
<th scope="col">등록자</th>
<th scope="col">플레이횟수</th>
<th scope="col">등록날짜</th>
</tr>
</thread>
<tbody th:each="game,index:${games}" id="accordion">
<tr data-bs-toggle="collapse" th:attr="title=${game.id}" onclick="toggleCollapse(this.title)" class="accordion-toggle">
<th class="index" scope="row" th:text="${index.index+1}">1</th>
<th class="title" th:text="${game.name}">test</th>
<th class="maker" th:text="${game.id}">member</th>
<th class="play-count" th:text="${game.playCount}">number</th>
<th class="time" th:text="${#temporals.format(game.getCreateTime(),'yyyy-MM-dd')}"></th>
</tr>
<tr>
<td colspan="5" >
<div class="collapse row" th:id="${game.id}">
<p class="col-md-9" th:text="${game.description}"></p>
<button class="col-md-3 btn btn-outline-danger" th:title="${game.id}" onclick="toGamePage(this.title)">► 플레이하기</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Controller
private final WorldCupGameService service;
@GetMapping(value = "/")
public String index(Model model){
model.addAttribute("games",service.getRepo().findAll());
return "main";
}
main.js
toggleCollapse=(id)=>{
console.log(id);
$('#'+id).collapse('toggle');
}
toGamePage=(id)=>{
window.location.href='/worldcup/'+id;
}
Controller에서는 딱히 특별한것은 없다. worldcupgame 목록 전체를 조회해서 넘겨줄 뿐이다.
front쪽에서는 thymeleaf를 써서 게임 목록을 표시하고 게임을 클릭하면 collapse를 사용해서 게임 설명과 play버튼이 나오도록 하였다.
그다음 게임 진행 page인 worldcup_play.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${data.name}">Title</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script type="text/javascript" src="/js/worldcup_play.js"></script>
</head>
<body>
<h3 class="current_title" id="game-current"></h3>
<input type="hidden" id="gamedata" th:value="${items}">
<div class="w-100 border-bottom border-dark" style="height: 80px">
<h1 class="w-100 text-center"><span th:text="${data.name}"></span><span id="worldcup-round"></span></h1>
</div>
<div class="container">
<div class="row h-100">
<div class="col-md-5 m-3 border border-primary" onclick="winsWorldCupItem(true)" title-="">
<img class="w-100 h-100" src="" id="img-left">
<h3 class="w-100 text-center" id="img-dsc-left"></h3>
</div>
<div class="col-md-5 m-3 border border-primary" onclick="winsWorldCupItem(false)" title="">
<img class="w-100 h-100" src="" id="img-right">
<h3 class="w-100 text-center" id="img-dsc-right"></h3>
</div>
</div>
</div>
</body>
</html>
게임진행 page의 이벤트 js인 worldcup_play.js
// let sessionStorage = window.sessionStorage;
let items = []
let idx= 0;
let winList = [];
let counter = 0;
// 이미지 클릭시 winList으로 클릭한 item push
// items 에 있는 게임을 모두 진행 하였다면 winList를 items으로 복사후 초기화 한다.
// 만약 승자가 나왔으면 그결과를 서버로 보내고 결과 페이지로 이동한다.
winsWorldCupItem=(isLeft)=>{
let index = isLeft? idx:idx +1;
winList.push(items[index]);
idx += 2;
if(idx >= items.length-1){
idx = 0;
items = Object.assign([], winList);
counter = 0;
winList.length = 0;
items.sort(()=>Math.random() - 0.5);
}
if(items.length == 1)
winGame(items[0]);
else
shiftImage(idx);
setRoundText();
}
// 이미지를 교체하기 위한 Logic
shiftImage=(idx)=>{
let leftImg = $('#img-left');
let leftDcs = $('#img-dsc-left');
let rightImg = $('#img-right');
let rightDcs = $('#img-dsc-right');
counter++;
leftImg.attr("src","/image/"+items[idx].id);
leftDcs.text(items[idx].title);
rightImg.attr("src","/image/"+items[idx+1].id);
rightDcs.text(items[idx+1].title);
}
// 게임을 승리하였을때 Logic
winGame=(item)=>{
$.ajax({
url:'/world-cup-game/'+item.id,
method:'POST',
success:(response)=>{
if(response == 'save'){
window.location.href='/world-cup-game/result/'+item.id
}
}
})
}
// Round 이름 변경하기
setRoundText=()=>{
$('#worldcup-round').text(' '+getRoundTitle()+' ('+String(items.length/2) +' / ' + String(counter)+')')
}
// 4개팀이 남았을때 준결승전, 2팀만 나왔을땐 결승전을 표시힌다.
getRoundTitle=()=>{
if(items.length > 4)
return String(items.length)+'강';
else if(items.length == 4)
return '준결승전';
else
return '결승전';
}
// 페이지가 준비되면 input에 담긴 Items를 받아서 JSON으로 파싱, items에 저장한다.
$(window).ready(()=>{
let tmp = $('#gamedata').val();
items = JSON.parse(tmp);
// sessionStorage.setItem("items",JSON.parse(tmp));
shiftImage(0);
setRoundText();
});
Controller
@GetMapping(value = "/worldcup/{id}")
@Transactional(readOnly = true)
public String toGamePage(@PathVariable("id") String id, Model model){
WorldCupGame game = service.getRepo().getById(Long.parseLong(id));
List<String> tmp = new ArrayList<>();
for(WorldCupItem item : game.getGames())
tmp.add(new WorldCupItemDTO(item).toString());
game.getGames().clear();
model.addAttribute("data",game);
model.addAttribute("items",tmp);
return "worldcup_play";
}
public class WorldCupItemDTO {
private long id;
private String title;
private int winCount;
public WorldCupItemDTO(WorldCupItem item){
this.id = item.getId();;
this.title = item.getTitle();
this.winCount = item.getWinCount();
}
@Override
public String toString() {
return "{\"id\":"+this.id+",\"title\":\""+this.title+"\",\"winCount\":"+winCount+"}";
}
}
처음에는 WorldCupItem을 반환하려고 했으나 계속 Large Object may not be used in auto-commit mode오류가 떴다. 찾아보니 Lob 형 데이터를 객체 불러올려면 @Transaction을 써야 한다고한다. 처음에는 이를 모르고 WorldCupItemDTO를 만들어서 문제를 해결하려고 했지만 만들고 생각해보니 어쨋든 Select 문에서 Lob도 선택을 하는건 마찬가지.. @Basic(fetch=FatchType.LAZY)를 주었는데도 해결이 않되었다. 결국 @Transaction(readyOnly =true)를 써서 auto-commit mode가 아니게 하여 해결했지만 데이터를 넘길때 ID,Title 값만 있으면 되므로 그냥 만들어둔 DTO를 사용하기로 했다. (이미지 불러오는것은 따로 API를 작성하였다.)
그림 선택 이벤트를 설계할때 Cookie나 Session같은 저장소를 활용할까 생각했다. 하지만 굳이 페이지를 이동하면서까지 WorldcupItem 정보를 가지고 있을 필요가 없어서 그냥 javascript를 사용해서 구현하였다.
마지막 우승 결과 페이지
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="title">Title</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</head>
<body>
<div class="container p-3">
<table class="table">
<tbody>
<tr>
<th>
<img class="rounded float-left" th:src="'/image/'+${winitem.id}" height="200"/>
</th>
<th>
<h3 th:text="${winitem.title}"></h3>
</th>
<th>
<h3 th:text="${#numbers.formatDecimal((winitem.winCount/playcount)*100,0.0,2)+'%'}"></h3>
</th>
</tr>
</tbody>
</table>
</div>
<table class="table">
<thead>
<tr>
<th scope="col">Image</th>
<th scope="col">Title</th>
<th scope="col">승리 비율</th>
</tr>
</thead>
<tbody th:each="result : ${results}">
<th>
<img class="rounded float-left" th:src="'/image/'+${result.id}" height="200"/>
</th>
<th>
<h3 th:text="${result.title}"></h3>
</th>
<th>
<h3 th:text="${(result.winCount/playcount)*100!=0?#numbers.formatDecimal((result.winCount/playcount)*100,0.0,2):'0'+'%'}"></h3>
</th>
</tbody>
</table>
</body>
</html>
Controller
@GetMapping("/world-cup-game/result/{id}")
@Transactional(readOnly = true)
public String gameResultPage(@PathVariable("id")String itemid,Model model){
long id = Long.parseLong(itemid);
WorldCupGame game = service.getRepo().findByItemId(id);
List<WorldCupItem> results = game.getGames();
WorldCupItem winItem = results.stream().filter(e->e.getId() == id).findFirst().get();
results.remove(winItem);
service.sortByWincount(results);
model.addAttribute("playcount",(double)game.getPlayCount());
model.addAttribute("winitem",winItem);
model.addAttribute("results",results);
return "worldcup_result";
}
아직 댓글같은 기능을 구현하지 않았기 때문에 딱히 이벤트를 정의하지는 않았다.
원래는 로그인, 댓글같은 기능까지도 구현하려고 했으나 다른 할일이 생각보다 이번프로젝트는 여기에서 마치려고 한다.(사실 backend까지는 구현해놨으나 front 구현하기가 너무 귀찮아....)
최종적인 코드는 아래 Github에 업로드 하였다.
https://github.com/BGHyeon/image_worldcup_game
GitHub - BGHyeon/image_worldcup_game: simple image worldcup game with spring boot
simple image worldcup game with spring boot. Contribute to BGHyeon/image_worldcup_game development by creating an account on GitHub.
github.com
다음프로젝트는 뭘로 할까나...
'Project > [Spring] 이미지 월드컵 만들기' 카테고리의 다른 글
[Spring]이미지 월드컵 만들기 (6)- 이미지 업로드(2) (0) | 2022.05.24 |
---|---|
[Spring]이미지 월드컵 만들기(5) - 이미지 업로드(1) (0) | 2022.05.23 |
[Spring]이미지 월드컵 만들기(4) - 기능 설계하기 (1) | 2022.05.18 |
[Spring]이미지 월드컵 만들기(3) - DB Entity 설계하기 (0) | 2022.05.18 |
[Spring]이미지 월드컵 만들기(2) - Database 연결 (0) | 2022.05.18 |