sm 기술 블로그

Ajax로 좋아요 버튼 기능 구현(with Thymeleaf) 본문

토이프로젝트

Ajax로 좋아요 버튼 기능 구현(with Thymeleaf)

sm_hope 2022. 8. 20. 17:17

Ajax란?

https://smhope.tistory.com/481

 

Ajax란?

Asynchronous JavaScript and XML 의 약자 자바스크립트를 이용해서 비동기식으로 서버와 통신하는 방식. 이 때 XML을 이용한다. 꼭 XML을 이용할 필요는 없고, 최근에는 json을 더 많이 이용한다. 비동기

smhope.tistory.com

1. DB

CREATE TABLE `test`(
   id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
   is_like INT(2) NOT NULL
);

INSERT INTO `test` SET
is_like = 0;

SELECT * FROM `test`;

 

테스트 테이블을 하나 생성해준다.

만약 is_like가 1이면 빨간색 하트를 0이라면 빈 하트를 보여줄 것이다.

 

2. Entity

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Data
public class Test {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private Integer isLike;
}

다음과 같이 entitiy를 생성해 준다.

 

3. Repository

import org.springframework.data.jpa.repository.JpaRepository;

public interface TestRepository extends JpaRepository<Test,Long> {
}

 

4. Controller

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class TestController {

    private final TestRepository testRepository;

    @GetMapping("/testAjax")
    public String test(Model model){
        List<Test> testList = testService.getList();
        model.addAttribute("testList",testList);
        return "test.html";
    }

    @PostMapping("/testAjax/{testId}")
    @ResponseBody
    public Integer createAnswer(@PathVariable("testId") long testId) {
        Test test = testRepository.findById(testId).get();

        if(test.getIsLike() == 1){
            test.setIsLike(0);
            testRepository.save(test);
            return 0;
        }

        else{
            test.setIsLike(1);
            testRepository.save(test);
            return 1;
        }

    }

}

테스트로 진행하는 것이기 때문에 서비스를 거치지지 않고 바로 리포지터리와 연결을 진행하겠다.

 

먼저 testAjax도메인으로 들어가면 test템플릿이 나오도록 할것이다.

 

그 템플릿에는 우리가 만든 test entity를 넣어 보낼 것이다.

리스트로 한 이유는 여러개의 버튼을 구현하고자 하기 때문이다.

 

@PostMapping("/testAjax/{testId}")은 뒤에 ajax와 함께 설명을 진행하겠다.

 

5. Templates

(1) HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css">
<script src="https://code.jquery.com/jquery-3.4.1.js"></script>
<div>
    DB내용을 출력해 보아요!
    <div th:each="test : ${testList}">
          <div th:if="${test.isLike==1}" class="likeContent">
              <button type="button" th:onclick="'javascript:test('+${test.id}+')'">
                  <div th:id="'like'+${test.id}">
                      <img src="https://previews.123rf.com/images/briang77/briang771512/briang77151202634/49684331-%EC%8B%AC%EC%9E%A5-%EB%B2%A1%ED%84%B0-%EC%95%84%EC%9D%B4%EC%BD%98.jpg" alt="" width="30px" height="30px">
                  </div>
              </button>
          </div>
          <div th:if="${test.isLike==0}" class="likeContent">
              <button type="button" th:onclick="'javascript:test('+${test.id}+')'">
                  <div th:id="'like'+${test.id}">
                      <img src="https://previews.123rf.com/images/wektorygrafika/wektorygrafika1608/wektorygrafika160800027/61006257-%EC%9D%B8%EA%B0%84%EC%9D%98-%EB%A7%88%EC%9D%8C-%EC%8B%A4%EB%A3%A8%EC%97%A3-%EA%B0%9C%EC%9A%94-%EC%82%AC%EB%9E%91-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%9D%B0%EC%83%89-%EB%B0%B0%EA%B2%BD%EC%97%90-%EA%B3%A0%EB%A6%BD-%EB%90%9C-%EB%B2%A1%ED%84%B0-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-%EB%A0%88%EC%9D%B4-%EC%85%98.jpg" alt="" width="30px" height="30px">
                  </div>
              </button>
          </div>
    </div>
</div>
</body>
</html>

Jquery를 사용하기 위해 연결을 해주었다.

테일윈드는 버튼 노말라이즈를 위해 사용하였다. (css를 쓰기 귀찮아서 쓴 거 맞다.)

 

컨트롤러에서 우리는 list형태로 템플릿에게 전달하였다.

그 말은 여러개의 테스트케이스가 있다는 것을 뜻하여 for Each문을 통해 반복하여 주자.

 

만약 isLike가 1이라면 빨간색 하트를, 아니라면 빈하트를 보여주기 위해 if문을 사용해 주었다.

<button type="button" th:onclick="'javascript:test('+${test.id}+')'">

버튼을 눌렀을 때 test라는 함수가 실행되는데 그때 test id를 매개변수로 하여 전송한다.

사용하는 매개변수는 test의 id이다.

이 방식 말고 더 좋은 방법이 있을 것 같긴 하지만 반복문에서 겹치치않고 id를 전달하는 방법은 이 방법 외에는 잘 모르겠다.

<div th:id="'like'+${test.id}">

id는 유니크다.

따라서 동일한 id가 존재 한다면 첫번째 id만 사용된다.

하지만 우리는 id별로 따로 동작을 하기를 원한다.

따라서 id뒤에 번호를 붙여 고유한 id를 지니도록 했다.

물론 여기도 이 방법 외에 더 좋은 방법이 있긴 하겠지만, 획기적인 아이디어가 떠오르지 않는다.

 

(2) JS [Ajax]

<script layout:fragment="script" type='text/javascript'>
    function test(id){
        $.ajax ({
            url : "/testAjax/"+id,
            type : "POST",
        })
        .done(function (fragment) {
            var result = "";
            if(fragment == 0){
                result =  "<div id=\"like"+id+"\">";
                result += "<img src='https://previews.123rf.com/images/wektorygrafika/wektorygrafika1608/wektorygrafika160800027/61006257-%EC%9D%B8%EA%B0%84%EC%9D%98-%EB%A7%88%EC%9D%8C-%EC%8B%A4%EB%A3%A8%EC%97%A3-%EA%B0%9C%EC%9A%94-%EC%82%AC%EB%9E%91-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%9D%B0%EC%83%89-%EB%B0%B0%EA%B2%BD%EC%97%90-%EA%B3%A0%EB%A6%BD-%EB%90%9C-%EB%B2%A1%ED%84%B0-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-%EB%A0%88%EC%9D%B4-%EC%85%98.jpg' alt='' width='30px' height='30px'>";
                result += "</div>";
            }
            else{
                result = "<div id=\"like"+id+"\">";
                result += "<img src='https://previews.123rf.com/images/briang77/briang771512/briang77151202634/49684331-%EC%8B%AC%EC%9E%A5-%EB%B2%A1%ED%84%B0-%EC%95%84%EC%9D%B4%EC%BD%98.jpg' alt='' width='30px' height='30px'>";
                result += "</div>";
            }
            $('#like'+id).replaceWith(result);
        });
    }
</script>

본격적으로 Ajax를 사용한다.

버튼을 눌렀을 때 우리는 test(현재 번호)로 함수를 호출한다.

따라서 function test(id) 에서 id는 버튼을 누른 곳에 해당하는 test id를 일컫는다.

 

        $.ajax ({
            url : "/testAjax/"+id,
            type : "POST",
        })

$.ajax의 셋팅으로 jQuery를 이용한 ajax 통신의 가장 기본적인 API이다.

 

url은 요청이 전송될 URL 주소이다.

type은 요청방식으로 POST를 사용하였다. (GET도 가능하다.)

 

ajax가 요청을 하면 아까 컨트롤러에서 작성한 url로 통신이 진행될 것이다.

    @PostMapping("/testAjax/{testId}")
    @ResponseBody
    public Integer createAnswer(@PathVariable("testId") long testId) {
        Test test = testRepository.findById(testId).get();

        if(test.getIsLike() == 1){
            test.setIsLike(0);
            testRepository.save(test);
            return 0;
        }

        else{
            test.setIsLike(1);
            testRepository.save(test);
            return 1;
        }

    }

현재 아이디를 통해 test를 조회한다.

IsLike가 만약 1이라면 0으로 바꿔 주고 0이면 1로 바꿔준다. (OnOff방식으로 진행)

 

그리고 return을 1혹은 0으로 해준다.

이 부분이 핵심이다.

 

@ResponseBody를 통해 return을 하면 Json 형태의 데이터를 return한다.

따라서 ajax는 요청 후 isLike의 결과에 따라 0혹은 1을 json데이터 형태로 반환받게 된다.

 

        .done(function (fragment) {
            var result = "";
            if(fragment == 0){
                result =  "<div id=\"like"+id+"\">";
                result += "<img src='https://previews.123rf.com/images/wektorygrafika/wektorygrafika1608/wektorygrafika160800027/61006257-%EC%9D%B8%EA%B0%84%EC%9D%98-%EB%A7%88%EC%9D%8C-%EC%8B%A4%EB%A3%A8%EC%97%A3-%EA%B0%9C%EC%9A%94-%EC%82%AC%EB%9E%91-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%9D%B0%EC%83%89-%EB%B0%B0%EA%B2%BD%EC%97%90-%EA%B3%A0%EB%A6%BD-%EB%90%9C-%EB%B2%A1%ED%84%B0-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-%EB%A0%88%EC%9D%B4-%EC%85%98.jpg' alt='' width='30px' height='30px'>";
                result += "</div>";
            }
            else{
                result = "<div id=\"like"+id+"\">";
                result += "<img src='https://previews.123rf.com/images/briang77/briang771512/briang77151202634/49684331-%EC%8B%AC%EC%9E%A5-%EB%B2%A1%ED%84%B0-%EC%95%84%EC%9D%B4%EC%BD%98.jpg' alt='' width='30px' height='30px'>";
                result += "</div>";
            }
            $('#like'+id).replaceWith(result);
        });
    }

반환 받은 데이터는 fragment 로 받게 된다.

(통신을 통해 JSON형태로 반환되었기 때문, 만약 반환 받은게 없다면 스크립트 구문을 출력함.)

 

받은 값이 0이라면 하트가 비게 만드는 내용으로 작성하고, 1이라면 빨간 하트가 출력되도록 내용을 작성한다.

 

여기서 div태그를 다시 써주는 이유는 replaceWith을 진행하면서 기존 div를 먹는 현상을 발견했기 때문이다.

다른 방법도 있을 듯 하지만 그냥 다시 작성하는 방식을 사용하였다.

 

마지막으로 like+id를 가진 태그에 위에서 작성한 result를 덮어 씌운다.

 

(3) Templates 전체

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css">
<script src="https://code.jquery.com/jquery-3.4.1.js"></script>
<div>
    DB내용을 출력해 보아요!
    <div th:each="test : ${testList}">
        <input type="hidden" class="test" th:value="${test.id}">
            <div th:if="${test.isLike==1}" class="likeContent">
                <button type="button" th:onclick="'javascript:test('+${test.id}+')'">
                    <div th:id="'like'+${test.id}">
                        <img src="https://previews.123rf.com/images/briang77/briang771512/briang77151202634/49684331-%EC%8B%AC%EC%9E%A5-%EB%B2%A1%ED%84%B0-%EC%95%84%EC%9D%B4%EC%BD%98.jpg" alt="" width="30px" height="30px">
                    </div>
                </button>
            </div>
            <div th:if="${test.isLike==0}" class="likeContent">
                <button type="button" th:onclick="'javascript:test('+${test.id}+')'">
                    <div th:id="'like'+${test.id}">
                        <img src="https://previews.123rf.com/images/wektorygrafika/wektorygrafika1608/wektorygrafika160800027/61006257-%EC%9D%B8%EA%B0%84%EC%9D%98-%EB%A7%88%EC%9D%8C-%EC%8B%A4%EB%A3%A8%EC%97%A3-%EA%B0%9C%EC%9A%94-%EC%82%AC%EB%9E%91-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%9D%B0%EC%83%89-%EB%B0%B0%EA%B2%BD%EC%97%90-%EA%B3%A0%EB%A6%BD-%EB%90%9C-%EB%B2%A1%ED%84%B0-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-%EB%A0%88%EC%9D%B4-%EC%85%98.jpg" alt="" width="30px" height="30px">
                    </div>
                </button>
            </div>
    </div>
</div>
</body>
<script layout:fragment="script" type='text/javascript'>
    function test(id){
        $.ajax ({
            url : "/testAjax/"+id,
            type : "POST",
        })
        .done(function (fragment) {
            var result = "";
            if(fragment == 0){
                result =  "<div id=\"like"+id+"\">";
                result += "<img src='https://previews.123rf.com/images/wektorygrafika/wektorygrafika1608/wektorygrafika160800027/61006257-%EC%9D%B8%EA%B0%84%EC%9D%98-%EB%A7%88%EC%9D%8C-%EC%8B%A4%EB%A3%A8%EC%97%A3-%EA%B0%9C%EC%9A%94-%EC%82%AC%EB%9E%91-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%9D%B0%EC%83%89-%EB%B0%B0%EA%B2%BD%EC%97%90-%EA%B3%A0%EB%A6%BD-%EB%90%9C-%EB%B2%A1%ED%84%B0-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-%EB%A0%88%EC%9D%B4-%EC%85%98.jpg' alt='' width='30px' height='30px'>";
                result += "</div>";
            }
            else{
                result = "<div id=\"like"+id+"\">";
                result += "<img src='https://previews.123rf.com/images/briang77/briang771512/briang77151202634/49684331-%EC%8B%AC%EC%9E%A5-%EB%B2%A1%ED%84%B0-%EC%95%84%EC%9D%B4%EC%BD%98.jpg' alt='' width='30px' height='30px'>";
                result += "</div>";
            }
            $('#like'+id).replaceWith(result);
        });
    }
</script>
</html>

 

6. 결과본

'토이프로젝트' 카테고리의 다른 글

[자바] 문자열 계산기 (스택이용)  (0) 2022.06.10
[리액트 React] 명언게시판  (0) 2022.05.28
리액트 계산기  (0) 2022.05.19
Comments