85일차

손님 상품 목록페이지 상품페이지
추가할것
빈칸이면 submit버튼 비활성화
검색취소버튼
이전이후맨첨맨마지막 버튼 아이콘으로 변경

1. 빈칸이면 submit버튼 비활성화

회원가입에서 사용했던것을 착안하여 만들었다. 처음 변수 3가지를 false로두고
값을 입력할때마다 체크하는 이벤트를 만들었다.
입력이벤트마다 false로 만들어서 체크하는 대신 값이 들어있거나 빈문자열이 아니라면 true로 바꾸고 다시 함수를 실행해서
값을 채울때마다 disalbled를 추가하거나 제거하는 코드를 작성했다.

let productName = false;
let price = false;
let stockQuantity = false;

function enableSubmit() {
    if (productName && price && stockQuantity) {
        $("#addBtn").removeAttr("disabled");
    } else {
        $("#addBtn").attr("disabled", "");
    }
}

$("#productNameInput").keyup(function() {
    productName = false;
    const productNameInput = $("#productNameInput").val();
    if (productNameInput != '' && productNameInput != null) {
        productName = true;
    }
    enableSubmit()
});

$("#stockQuantityInput").keyup(function() {
    stockQuantity = false;
    const stockQuantityInput = $("#stockQuantityInput").val();
    if (stockQuantityInput != '' && stockQuantityInput != null) {
        stockQuantity = true;
    }
    enableSubmit()
});

$("#priceInput").keyup(function() {
    price = false;
    const priceInput = $("#priceInput").val();
    if (priceInput != '' && priceInput != null) {
        price = true;
    }
    enableSubmit()
});

2. 4개의 목록 씩 돌아가는데 8개씩 보여주기

부트스트랩 탬플릿이라는것을 찾았다.
view의 디자인적인 측면인데 4개씩 8개의 목록을 한페이지에 보여주기로 하였다.

<section class="py-5">
    <div class="container-lg">
        <div class="d-flex">
            <div class="me-auto">
            </div>
            <div>
                <form id="form1" action="/admin/product/list" class="d-flex" role="search">
                    <div class="input-group">
                        <select class="form-select flex-grow-0" style="width: 120px;" name="type">
                            <option value="all">전체</option>
                            <option value="productName" ${param.type eq 'productName' ? 'selected' : ''}>상품명</option>
                            <option value="countryOfOrigin" ${param.type eq 'countryOfOrigin' ? 'selected' : ''}>원산지</option>
                            <option value="categoryName" ${param.type eq 'categoryName' ? 'selected' : ''}>카테고리</option>
                        </select>
                        <input name="search" class="form-control" type="search" placeholder="Search" aria-label="Search" value="${param.search }">
                        <button class="btn btn-outline-success" type="submit">
                            <i class="fa-solid fa-magnifying-glass"></i>
                        </button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <div class="container px-4 px-lg-5 mt-5">
        <div class="row gx-4 gx-lg-5 row-cols-2 row-cols-md-3 row-cols-xl-4 justify-content-center">
            <c:forEach items="${productList}" var="product" varStatus="status">
                <div class="col mb-5">
                    <div class="card h-100">
                        <!-- Product image-->
                        <img class="card-img-top" src="${bucketUrl}/product/1.png" alt="사진준비중!" />
                        <!-- Product details-->
                        <div class="card-body p-4">
                            <div class="text-center">
                                <!-- Product name-->
                                <h5 class="fw-bolder">${product.productName}</h5>
                                <!-- Product price-->
                                ${product.price}원
                            </div>
                        </div>
                        <!-- Product actions-->
                        <div class="card-footer p-4 pt-0 border-top-0 bg-transparent">
                            <div class="text-center">
                                <a class="btn btn-outline-dark mt-auto" href="/customer/product/detail/${product.productId}">상품 상세 보기</a>
                            </div>
                        </div>
                    </div>
                </div>
                <!-- Line break after every 4 products -->
                <c:if test="${status.index % 4 == 3}">
                    <div class="w-100"></div>
                </c:if>
            </c:forEach>
        </div>
</section>

3. 페이지네이션

3.1 컨트롤러

며칠 후 조금의 변경이 있었다.
페이지 파라미터를 받는다.

// 상품 목록 view
@GetMapping("listView")
public ResponseEntity<Map<String, Object>> listView(
        @RequestParam(value = "page", defaultValue = "1") Integer page,
        @RequestParam(value = "category", required = false) Integer categoryId,
        @RequestParam(value = "type", required = false) String type,
        @RequestParam(value = "search", defaultValue = "") String search,
        Authentication authentication) {

    Map<String, Object> result = productService.getViewList(page, categoryId, type, search, authentication);
    return ResponseEntity.ok(result);
}

3.2 서비스

8개씩 보여줄것이기 때문에 pageSize는 8이다.
startIndex는 0 8 16 이런식으로 가기때문에 (page - 1) * pageSize를 해주었다.
숫자는 10개가 보이는데 구글 검색의 방법으로 현재페이지기준 좌5개 우4개를 보여주기 위해
leftPageNumber는 page - 5 rightPageNumber는 leftPageNumber + 9 로 정했다.
prevPageNumber와 nextPageNumber는 상품관리페이지와는 다르게 현재페이지기준으로 좌 우로 가게 변경하였다.

@Override
public Map<String, Object> getViewList(Integer page, Integer categoryId, String type, String search,
        Authentication authentication) {
    // 0~10 10~20 20~30
    Integer pageSize = 8; // 8 16 24
    Integer startIndex = (page - 1) * pageSize; // 0 8 16

    // 페이지 네이션에 필요한 정보
    // 전체 레코드 수
    Integer count = productMapper.countCustomerAll(categoryId, type, search);

    // 마지막 페이지 번호
    // 총 글개수 -1 / pageSize + 1
    Integer lastPage = (count - 1) / pageSize + 1;

    // 페이지 기준 6페이지면 1~10 좌 5 현재페이지 우 4
    // 페이지네이션 왼쪽번호
    Integer leftPageNumber = page - 5;
    leftPageNumber = Math.max(leftPageNumber, 1);

    // 페이지네이션 오른쪽 번호
    Integer rightPageNumber = leftPageNumber + 9;
    rightPageNumber = Math.min(rightPageNumber, lastPage);

    // 현재 페이지
    Integer currentPageNumber = page;

    // 이전페이지
    Integer prevPageNumber = currentPageNumber - 1;

    // 다음 페이지
    Integer nextPageNumber = currentPageNumber + 1;

    Map<String, Object> pageInfo = new HashMap<>();
    pageInfo.put("lastPageNumber", lastPage);
    pageInfo.put("leftPageNumber", leftPageNumber);
    pageInfo.put("rightPageNumber", rightPageNumber);
    pageInfo.put("currentPageNumber", currentPageNumber);
    pageInfo.put("prevPageNumber", prevPageNumber);
    pageInfo.put("nextPageNumber", nextPageNumber);

    List<ProductView> productList = productMapper.selectCustomerAllPaging(pageSize, startIndex, categoryId, type,
            search);

    return Map.of("pageInfo", pageInfo, "productList", productList);
}

3.3 매퍼

고객에게는 공개된 상품들만 있어야하기 때문에
먼저 서브쿼리로 pub = 1인 상품들만 거른 테이블로 시작을 했다.
if절의 경우 검색인데 검색의 경우에는 다음에서 설명하도록 하겠다.

<select id="selectCustomerAllPaging" resultMap="productViewMap">
    <bind name="pattern" value="'%' + search + '%'" />
    SELECT * FROM (SELECT * FROM productview WHERE pub = 1) p
    <where>
        <if test="type eq 'all'">
            (product_name LIKE #{pattern} OR country_of_origin LIKE
            #{pattern})
        </if>
        <if test="type eq 'productName'">
            OR product_name LIKE #{pattern}
        </if>
        <if test="type eq 'countryOfOrigin'">
            OR country_of_origin LIKE #{pattern}
        </if>
        <if test="categoryId != null">
            AND category_id = #{categoryId}
        </if>
    </where>
    ORDER BY product_id DESC
    LIMIT #{startIndex}, #{pageSize}
</select>

4. 검색기능

1.카테고리별로 나누기
2.검색기능으로 넣기
두가지의 분류로 나누어서 진행하게 되었다.
카테고리 탭과 검색 탭이 따로 있지만 쿼리문은 동일하기 때문에 이를 유지하기 위한 생각을 많이 하게 되었다.

4.1 자바스크립트 코드

카테고리를 각각 누를때 현재 url을 가져와서 파라미터를 얻도록했다.
쿼리를 더하는 함수인 addQueryParam를 만들어서 현재 url과 category(쿼리이름) 쿼리명을 값으로 전달했다.
각 링크에 따라서 쿼리스트링이름과 값이 들어가게 되는데 all일 경우에는 값을 지워 모든것이 나오게 하고 싶었다.

addQueryParam함수에서는 현재 url을 받아 searchParams메소드를 사용해서 새 파라미터명과 값을 set()메소드로 넣었다.
자바스크립트의 내장 함수이다.

그러면서도 각 탭을 누르면 page는 맨처음으로 돌아가게 하고 싶었기 때문에 page파라미터를 항상 삭제해주도록 하였다.

// jQuery 코드: 각 링크 클릭 시 현재 URL의 파라미터 유지하고 category 파라미터 추가
const currentUrl = window.location.href; // 현재 URL 가져오기

const cateLink = addQueryParam(currentUrl, 'category', 'all');
$('#allLink').attr('href', cateLink);

const beefLink = addQueryParam(currentUrl, 'category', '1');
$('#beefLink').attr('href', beefLink);

const porkLink = addQueryParam(currentUrl, 'category', '2');
$('#porkLink').attr('href', porkLink);

const chickenLink = addQueryParam(currentUrl, 'category', '3');
$('#chickenLink').attr('href', chickenLink);

// jQuery 함수: 현재 URL에 파라미터 추가 및 수정
function addQueryParam(url, paramName, paramValue) {
    const urlObj = new URL(url);
    if (paramValue === 'all') {
        urlObj.searchParams.delete(paramName);
    } else {
        urlObj.searchParams.set(paramName, paramValue);
    }
    urlObj.searchParams.delete('page'); // 페이지 파라미터를 항상 1로 설정
    return urlObj.toString();
}

4.2 컨트롤러 서비스

컨트롤러와 서비스는 페이지 네이션과 같다.
총 페이지 수를 검색기능에 따라 알아야하기 때문에 총페이지수의 쿼리에 값을 넣어주기만했다.

4.3 매퍼

쿼리문을 작성하는데 우여곡절이 많았다. 카테고리를 선택하는 것과 검색을 하느 것에 있어서 따로 분리되어 있엇고
유지한 파라미터를 이용해서 값을 전달하는데 어떤 파라미터가 없는 경우가 발생하기도 한다.
또한 or or and로 작성하면 and로 마지막 조건처리를 해야하는데 앞의 조건이 만족하는 경우 다 나오게 되버리는 문제가 있었다.
그래서 ()를 사용해서 or값들을 먼저 처리한 후 and로 마지막 조건처리를 하고 싶었다.
이런경우는 검색하는 type이 all일경우이기만하면 되었기 때문에 all 일경우 모든 파라미터가 들어가고 ()안에서 검색되는 쿼리를 만들었다.
나머지는 마이바티스의 where구문을 활용해서 조건에 맞을경우만 or이나 and가 들어가기 때문에 문제가 없이 넘어갈 수 있었다.

<select id="selectCustomerAllPaging" resultMap="productViewMap">
    <bind name="pattern" value="'%' + search + '%'" />
    SELECT * FROM (SELECT * FROM productview WHERE pub = 1) p
    <where>
        <if test="type eq 'all'">
            (product_name LIKE #{pattern} OR country_of_origin LIKE
            #{pattern})
        </if>
        <if test="type eq 'productName'">
            OR product_name LIKE #{pattern}
        </if>
        <if test="type eq 'countryOfOrigin'">
            OR country_of_origin LIKE #{pattern}
        </if>
        <if test="categoryId != null">
            AND category_id = #{categoryId}
        </if>
    </where>
    ORDER BY product_id DESC
    LIMIT #{startIndex}, #{pageSize}
</select>

2023.05.26

우여곡절들은 본문에 들어가 있다.
쿼리문을 작성하는데 많은 힘을 사용하게 되는 것 같다.
기본 view페이지를 만들었는데 정말 사소하지만 시간이 많이드는 일이다.

'국비 > Project-2 쇼핑몰' 카테고리의 다른 글

2023.05.31 87일차 Team Project  (0) 2023.06.06
2023.05.30 86일차 Team Project  (0) 2023.05.31
2023.05.25 84일차 Team Project  (0) 2023.05.31
2023.05.24 83일차 Team Project  (0) 2023.05.24
2023.05.23 82일차 -1 Project  (0) 2023.05.24

+ Recent posts