[portfolio] 그리드 목록 화면 구현(Toast Grid)

박이레·2023년 10월 24일
0

portfolio

목록 보기
14/20

 그리드를 그리는(drawing) Toast Grid.ㅂㅂ


완성된 화면


jsp 코드

<div class="gridWrapper">
	<div id="grid"></div>
    <div id="pagination" class="tui-pagination"></div>
</div>

js 코드

$(function() {
  review.init();
});

let review = {

  seq: null,
  pagination : null,
  cnt : null,
  limit : 11,
  currentPage: 1,

  init: function () {
    let _this = this;

    let Grid = tui.Grid;
    Grid.applyTheme('clean');

    this.grid = new Grid({
      el: document.getElementById('grid'),
      scrollX: false,
      scrollY: false,
      bodyHeight: 450,
      rowHeaders: ['checkbox'],
      columns: [
        {
          header: "게시글 번호",
          name: "review_id",
          align: "center",
        },
        {
          header: "글쓴이",
          name: "writer",
          align: "center",
        },
        {
          header: "제목",
          name: "title",
          align: "center",
        },
        {
          header: "내용",
          name: "content",
          align: "center",
          resizable: true,
          editor: 'text',
        },
        {
          header: "작성일자",
          name: "create_dt",
          align: "center",
        },
        {
          header: "수정일자",
          name: "update_dt",
          align: "center",
        },
      ],
    });

    /* 데이터 총 개수 세는 함수 */
    this.cnt = this.readCnt();

    /* 초기 데이터 읽어오는 함수 */
    let list = this.read();

    /* 페이지네이션 초기화하는 함수 */
    this.pagination = new tui.Pagination(document.getElementById('pagination'),    {
      visiblePages: 5, // 한 번에 보여줄 1,2,3,4 목차 개수
      totalItems : this.cnt, // 전체 아이템 개수가 몇 개인지
      itemsPerPage: this.limit, // 한 페이지에 몇 개씩 보여줄 것인지
      centerAlign: true // 현재 선택된 페이지 중앙 정렬
    });


    /* 읽어온 데이터 그리드에 그리는 함수 */
    if (list) {
      this.grid.resetData(list);
      this.pagination.setTotalItems(this.cnt);
    }

    /* 페이지 이동 시 실행되는 함수 */
    this.pagination.on('afterMove', function (ev) {
      _this.currentPage = ev.page;
      let list = _this.read();
      if (list) {
        _this.grid.resetData(list);
        _this.pagination.setTotalItems(_this.cnt);
      }
    });

    /* 검색창 관련 함수 */
    const searchEl = $(".search");
    const searchInputEl = searchEl.find("input");

    searchEl.click(function () {
      searchInputEl.focus();
      searchInputEl.val('');
    })

    searchInputEl.on("focus", function() {
      searchEl.addClass("focused");
      searchInputEl.attr("placeholder", "검색할 내용을 입력하세요.");
    });

    searchInputEl.on("blur", function() {
      searchEl.removeClass("focused");
      // searchInputEl.val('');
      searchInputEl.attr("placeholder", "");
    });

    /* 검색창 enterkey 이벤트 */
    $(".search_input").on("keydown", function(e){
      if(e.keyCode === 13) {
        let list = _this.read();
        if (list) {
          _this.grid.resetData(list);
          _this.cnt = _this.readCnt();
          _this.pagination.setTotalItems(_this.cnt);
        }
      }
    });


    /* 게시글 상세보기 */
    this.grid.on('click', (ev) => {
      let _this = this;
      let selectedColumn = ev.columnName;

      /* 내용을 선택하는 경우에만 수행 */
      if (selectedColumn != "content") return;

      /* Column을 클릭했을 때만 수행 */
      let focuesCell = this.grid.getFocusedCell();

      if (focuesCell) {

        let review_id = _this.grid.getRow(ev.rowKey).review_id;
        this.readOne(review_id);
      }
    });
  },

  /* CRUD 함수들 */
  /* CREATE */
  create: function (data) {
    let _this = this;

    data = data;

    $.ajax({
      type: "PUT",
      url: "/review",
      async: false,
      contentType:"application/json; charset=utf-8",
      data: JSON.stringify({
        title: data.title,
        content: data.content
      }),
      success: function(response) {
        $("dialog").hide();
        _this.read();
      },
      error: function () {

      }
    })
  },

  /* READ */
  read: function() {
    let _this = this;
    let data;

    $.ajax({
      type:"POST",
      url:"/review",
      async: false,
      contentType:"application/json; charset=utf-8",
      data: JSON.stringify({
        page: this.currentPage, // 현재 페이지
        limit: this.limit, // 한번에 몇 개의 데이터를 보여줄 것인지
        content: $(".search_input").val(),
      }),
      success: function(response){
        data = response;
        _this.grid.resetData(data);
        // _this.pagination.setTotalItems(response.length);
      },
      error: function(response) {
        console.log(response);
        swal({
          title: response.responseText,
          type: 'warning'
        })
      }
    });
    return data;
  },
  
  /* CRUD 도와주는 함수들 */
  /* 데이터 개수 세는 함수 */
  readCnt: function () {
    let _this = this;
    let cnt;

    $.ajax({
      type: "POST",
      url: "/review/cnt",
      async: false,
      contentType:"application/json; charset=utf-8",
      data: JSON.stringify({
        content: $(".search_input").val(),
      }),
      success: function(response){
        cnt = response;
      },
      error: function() {
      }
    });
    return cnt;
  },

xml 코드

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.iraefolio.mapper.ReviewMapper">

    <select id="read" resultType="com.iraefolio.domain.ReviewEntity">
        SELECT * FROM TB_REVIEW
        <if test="CONTENT != null and !CONTENT.equals('')">
            WHERE CONTENT LIKE CONCAT('%', #{CONTENT}, '%')
        </if>
        ORDER BY REVIEW_ID DESC
        LIMIT #{limit} OFFSET #{offset}
    </select>

    <select id="readOne" resultType="com.iraefolio.domain.ReviewEntity">
        SELECT * FROM TB_REVIEW
        WHERE REVIEW_ID = #{REVIEW_ID}
    </select>

    <select id="readCnt" resultType="Integer">
        SELECT COUNT(*)
        FROM TB_REVIEW
        <if test="CONTENT != null and !CONTENT.equals('')">
            WHERE CONTENT LIKE CONCAT('%', #{CONTENT}, '%')
        </if>
    </select>

    <insert id="create">
        INSERT INTO TB_REVIEW(TITLE, CONTENT, CREATE_DT)
        VALUES (#{TITLE}, #{CONTENT}, current_timestamp)
    </insert>

    <update id="update">
        UPDATE TB_REVIEW SET
            UPDATE_DT = current_timestamp
        <if test="isUpdated">
            , CONTENT = #{CONTENT}
        </if>
        WHERE REVIEW_ID = #{REVIEW_ID}
    </update>

    <delete id="delete">
        DELETE FROM TB_REVIEW
        WHERE REVIEW_ID = #{REVIEW_ID}
    </delete>
</mapper>

Controller

package com.iraefolio.controller;

import com.iraefolio.domain.ReviewEntity;
import com.iraefolio.service.ReviewService;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Tag(name = "review Controller", description = "review Controller")
@Log4j2
@RequiredArgsConstructor
@RequestMapping("/review")
@PreAuthorize("hasRole('USER')")
@RestController
public class ReviewController {

    private final ReviewService service;

    /* PAGING */
    @Operation(summary = "review paging", description = "/review로 이동합니다.")
    @GetMapping
    public ModelAndView review() {
        ModelAndView mav = new ModelAndView("review");
        return mav;
    }

    /* READ */
    @Operation(summary = "READ review data", description = "/review의 데이터를 읽어옵니다.")
    @PostMapping
    public ResponseEntity read(@Valid @RequestBody ReviewEntity entity, BindingResult bindingResult) throws Exception {

        if (bindingResult.hasErrors()) throw new BindException(bindingResult);

        List<ReviewEntity> list = service.read(entity);

        return ResponseEntity.ok(list);
    }

    /* READONE */
    @Operation(summary = "READ review data by seq", description = "/review의 데이터를 한 개 읽어옵니다.")
    @GetMapping("/reviewDetail")
    public ModelAndView readOne(@RequestParam Integer review_id) throws Exception {
        ReviewEntity data = service.readOne(review_id);
        ModelAndView mav = new ModelAndView("/reviewDetail");
        mav.addObject("data", data);
        return mav;
    }

    /* CNT */
    @Operation(summary = "READ review cnt", description = "/review의 데이터 개수를 읽어옵니다.")
    @PostMapping("/cnt")
     public Integer readCnt(ReviewEntity entity) throws Exception {
        Integer cnt = service.readCnt(entity);
        return cnt;
    }

    /* CREATE */
    @Operation(summary = "CREATE review", description = "새로운 review를 작성합니다.")
    @PutMapping
    public void create(@RequestBody ReviewEntity entity) throws Exception {
        service.create(entity);
    }

    /* UPDATE */
    @Operation(summary = "UPDATE review", description = "review를 수정합니다.")
    @PatchMapping
    public void update(@RequestBody List<ReviewEntity> entity) throws Exception {
        service.update(entity);
    }
}

Service

package com.iraefolio.service;

import com.iraefolio.domain.ReviewEntity;
import com.iraefolio.mapper.ReviewMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

@Log4j2
@RequiredArgsConstructor
@Service
public class ReviewService {

    private final ReviewMapper mapper;

    /* READ */
    public List<ReviewEntity> read(ReviewEntity entity) throws Exception {
        List<ReviewEntity> list = mapper.read(entity);
        return list;
    }

    /* READONE */
    public ReviewEntity readOne(Integer review_id) throws Exception {
        ReviewEntity data = mapper.readOne(review_id);
        return data;
    }

    /* ReadCnt */
    public Integer readCnt(ReviewEntity entity) throws Exception {
        Integer cnt = mapper.readCnt(entity);
        return cnt;
    }

    /* CREATE */
    public void create(ReviewEntity entity) throws Exception {
        mapper.create(entity);
    }

    /* UPDATE */
    public void update(List<ReviewEntity> entity) throws Exception {

        for (int i = 0; i < entity.size(); i++) {
            ReviewEntity e = entity.get(i);
            if (e.isUpdated()) {
                mapper.update(e);
            }
            if (e.isDeleted()) {
                mapper.delete(e);
            }
        }
    }
}

Pagination 위한 entity

package com.iraefolio.domain;

import com.fasterxml.jackson.annotation.JsonGetter;
import lombok.Data;

@Data
public class BaseEntity {

    @Schema(description = "CRUD 구분을 위한 flag", nullable = false)
    boolean isCreated;

    @Schema(description = "CRUD 구분을 위한 flag", nullable = false)
    boolean isUpdated;

    @Schema(description = "CRUD 구분을 위한 flag", nullable = false)
    boolean isDeleted;

    @Schema(description = "pagination에 사용하는 변수", nullable = false)
    private int limit;

    @Schema(description = "pagination에 사용하는 변수", nullable = false)
    private int page;

    @Schema(description = "pagination에 사용하는 변수", nullable = false)
    private int offset;

    @JsonGetter
    public int getOffset(){
        if(limit <= 0)
            limit = 10;
        if(page <= 0)
            page = 1;

        return (page - 1) * limit;
    }
}
profile
혜화동 사는 Architect

0개의 댓글