이제 프론트엔드 뷰를 구현할 차례이다. 지금까지 구현한 api들을 프론트엔드 뷰에 연결하는 작업을 해야한다.
필자는 컬럼/카드 리스트 조회 페이지를 구현하기로 했다.
결과 화면이다.
데이터를 가져오기 위한 서비스단 코드이다.
@Service
@RequiredArgsConstructor
public class ColumnsServices {
...
public ColumnsListResponseDto getColumnsList(Long id) {
Board board = findBoardById(id);
List<Card> cards = new ArrayList<>();
List<Columns> columnList = board.getColumnsList().stream().sorted(Comparator.comparing(Columns::getOrderNum)).toList();
List<CardResponseDto> cardResponseDtos = new ArrayList<>();
List<CategoryAndCardsResponseData> columns = new ArrayList<>();
for (int i = 0; i < columnList.size(); i++) {
cards.addAll(columnList.get(i).getCards().stream().sorted(Comparator.comparing(Card::getPosition)).toList());
cardResponseDtos = cards.stream().map(card -> new CardResponseDto(card)).toList();
columns.add(new CategoryAndCardsResponseData(columnList.get(i), cardResponseDtos));
cards = new ArrayList<>();
cardResponseDtos = new ArrayList<>();
}
List<ColumnsListResponseData> columnsListData = new ArrayList<>();
for (int i = 0; i < columns.size(); i++) {
columnsListData.add(new ColumnsListResponseData(columns.get(i)));
}
return new ColumnsListResponseDto(columnsListData);
}
...
}
이 서비스 메소드를 호출하면 이렇게 리턴된다.
{
"msg": "컬럼, 카드조회 성공",
"statuscode": "200",
"data": {
"columnsList": [
{
"column": {
"id": 1,
"category": "BackLog",
"cards": [
{
"position": 0,
"title": "카드1",
"content": "내용1",
"dueDate": "2024-07-17 00:00:00.000000",
"cardStatus": "BackLog"
},
...
]
}
},
...
]
}
}
다음 코드는 저 json 객체 구조를 만들기 위한 3개의 dto 객체들이다.
@Getter
@NoArgsConstructor
public class CardResponseDto {
private int position;
private String title;
private String content;
private String dueDate;
private CategoryEnum cardStatus;
// private String username;
public CardResponseDto(Card card) {
this.position = card.getPosition();
this.title = card.getTitle();
this.content = card.getContent();
this.dueDate = card.getDueDate();
this.cardStatus = card.getCardStatus();
}
}
@Getter
public class CategoryAndCardsResponseData {
private final Long id;
private final CategoryEnum category;
private final List<CardResponseDto> cards;
public CategoryAndCardsResponseData(Columns columns, List<CardResponseDto> cards) {
this.id = columns.getId();
this.category = columns.getCategory();
this.cards = cards;
}
}
@Getter
public class ColumnsListResponseData {
private final CategoryAndCardsResponseData column;
public ColumnsListResponseData(CategoryAndCardsResponseData column) {
this.column = column;
}
}
@Getter
public class ColumnsListResponseDto {
private final List<ColumnsListResponseData> columnsList;
public ColumnsListResponseDto(List<ColumnsListResponseData> columnsList) {
this.columnsList = columnsList;
}
}
원래는 엔티티를 그대로 가져올 목적이었다.
@OneToMany(mappedBy = "columns", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Card> cards = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "column_id")
private Columns columns;
하지만 컬럼 엔티티에는 전자가, 카드 엔티티에는 후자가 들어있는 바람에 컬럼을 호출하면 카드를 반환하고, 카드를 반환하면 또 다시 컬럼을 반환하는 순환 참조가 발생해서 무한루프를 돌게 되는 것이었다.
그래서 필자는 어쩔 수 없이 컬럼과 카드에서 필요한 컬럼을 추출해서 새로운 dto 객체를 만들어야만 했다. 문제는 객체 안에 객체가 있고, 그 객체 안에는 리스트 객체가 있고, 그 리스트 객체중 하나 안에는 또다른 객체가 있고... 이런 식으로 굉장히 복잡하게 얽혀있는 상태였다. 반환되는 json객체를 dto 이름으로 바꿔주면 이렇게 된다.
{
'Apiresponse' : {
...
'ColumnListResponseDto' : {
'List<ColumnListResponseData>' : [
'CategoryAndCardResponseData' : {
...
'List<CardResponseDto>' : [
...
]
}
]
}
이 구조대로 안쪽부터 역순으로 올라온다면 List<CardResponseDto> -> CategoryAndCardResponseData -> List<ColumnListResponseData> -> ColumnListResponseDto -> Apiresponse 순으로 dto 객체를 만들어야 했다.
CardResponseDto를 만들려면 Card엔티티가 필요하다. Card엔티티를 만들려면 Column이 필요하다.
List<Columns> columnList = board.getColumnsList().stream().sorted(Comparator.comparing(Columns::getOrderNum)).toList();
그래서 board엔티티를 가져오고 Column 리스트를 board 엔티티에서 가져왔다.
이제 각 컬럼별 카드 리스트를 가져와야 한다. 카드 리스트를 가져오고 카드 리스트를 stream으로 cardResponseDto 리스트 객체로 변환해주는걸 컬럼 리스트의 사이즈만큼 for문을 돌려서 반복했다.
컬럼 리스트의 사이즈만큼 생성된 CategoryAndCardResponseData 객체에 cardResponseDto리스트를 주입을 했다.
이렇게 해서 모은 CategoryAndCardResponseData객체를 리스트로 만들어서 ColumnListResponseData리스트에 주입을 했다.
마지막으로 ColumnListResponseData리스트를 ColumnListResponseDto에 주입을 하고 그걸 ApiResonse로 감싸서 ResponseEntity로 반환하면 서비스 로직이 종료된다.
만들다 보니 굉장히 복잡하게 구현되었다.
$(document).ready(function () {
$.ajax({
type: 'GET',
url: `/api/boards/[[${id}]]`,
success: function (response) {
var result = {
'columnsList': response.data.columnsList
}
addHTML(result);
}
})
})
function addHTML(result) {
for (let i = 0; i < result.columnsList.length; i++) {
let columns = result.columnsList[i];
let column_html = `
<div class="column">
<div class="column-header">${columns.column.category}</div>
<div id="columns${columns.column.id}">
</div>
<button class="add-card-btn" onclick="window.location.href = '/api/columns/${columns.column.id}/create/card'">+ Add Card</button>
</div>
`
$('#board').append(column_html);
for (let j = 0; j < columns.column.cards.length; j++) {
let card = columns.column.cards[j];
let card_html = `
<div class="task" draggable="true" data-id=${card.id} id="card${card.id}">
<div class="task-title">${card.title}</div>
<div class="task-description">${card.content}</div>
</div>
`
$(`#columns${columns.column.id}`).append(card_html);
}
}
}
api를 호출해서 받아온 데이터를 바탕으로 html 엘리먼트를 생성해서 반환하는 자바스크립트 함수이다. 카드를 생성할 때 카드 상세정보 페이지로 연결해 놓았다.
이렇게 컬럼/카드 리스트 조회가 끝나고 구현한 것은 컬럼 수정, 순서변경 api 연결과 ui를 만드는 작업이었다.
api를 연결하는 건 그리 어렵지 않았지만 저 화면을 구현하는 과정이 상당히 번거로웠다.
function addHTML(result) {
for (let i = 0; i < result.columnsList.length; i++) {
let columns = result.columnsList[i];
let column_html = `
<div class="column">
<div class="column-header">${columns.column.category} <button id="edit-column-button${i}" class="edit-column-btn" style="display: none" onclick="window.location.href = '/view/column/${columns.column.id}'">Edit Column</button></div>
<div class="cards" id="columns${columns.column.id}">
</div>
<button class="add-card-btn" onclick="window.location.href = '/view/columns/${columns.column.id}/create/card'">+ Add Card</button>
</div>
`
$('#board').append(column_html);
for (let j = 0; j < columns.column.cards.length; j++) {
let card = columns.column.cards[j];
let card_html = `
<button id="reorder-btn${card.id}" class="reorder-btn" onclick="toggleReorderModule(${card.id})" style="display: none">reorder</button>
<div class="task" draggable="true" data-id=${card.id} id="card${card.id}">
<div>${card.id}</div>
<div class="task-title">${card.title}</div>
<div class="task-description">${card.content}</div>
</div>
<div id="reorder-module${card.id}" style="display: none"><input type="text" id="new-position${card.id}"><button onclick="reorderApi(${card.position}, ${card.id})">앞으로 이동</button></div>
`
$(`#columns${columns.column.id}`).append(card_html);
}
}
}
기존 코드에 reorder, edit column버튼 등 여러 ui를 추가해 주었다. 평소에는 수정 관련 ui가 보이지 않다가 edit 버튼을 누르면 보이게 구현했다.
<h1>Your Kanban Board</h1>
<div>
<button id="edit-btn" class="edit-btn">Edit</button>
<button class="invite-btn">Invite Team Members</button>
</div>
</div>
$('#edit-btn').click(function () {
for (let i = 0; i < result.columnsList.length; i++) {
let columns = result.columnsList[i];
for (let j = 0; j < columns.column.cards.length; j++) {
let card = columns.column.cards[j];
$(`#reorder-btn${card.id}`).toggle();
}
$(`#edit-column-button${i}`).toggle();
}
})
다음은 카드 순서변경 화면을 구현하고 컬럼 수정화면을 만들었다..
Edit Column을 누르면 컬럼을 수정하는 api가 호출되고 delete column을 누르면 삭제하는 api가 호출된다.
reorder버튼을 누르면 사진처럼 텍스트박스와 버튼이 나타난다. editColumn을 누르면 수정화면으로 이동한다.
시간이 정말 늦었다. 나머지는 다음날 구현하겠다.
'내일배움캠프' 카테고리의 다른 글
Trello프로젝트(심화 프로젝트) 4일차 (6) | 2024.07.20 |
---|---|
최종프로젝트 1일차 (0) | 2024.07.18 |
Trello프로젝트(심화 프로젝트) 2일차 (0) | 2024.07.11 |
Trello프로젝트(심화 프로젝트) 1일차 (0) | 2024.07.11 |
JPA 심화과정 (0) | 2024.07.10 |