IT/development

[html] button에 type을 명시하자(feat. default submit)

알 수 없는 사용자 2023. 7. 9.

ajax로 페이징을 구현 했는데 페이지 이동이 되었다가 다시 같은 페이지가 호출이 되었다.

콘솔 보면 2페이지 데이터를 정상적으로 가져왔다가 다시 1페이지가 호출이 되고 있다.

간단한 원인 이었는데 꽤 많은 시간 삽질 끝에 나중에 다시 봐야지 하고 넘어 갔었다가 다시 봐서 해결 했다.

before source 🙂

/**
     * 페이지네이션을 그린다.
     * @param paging
     */
    function drawPagination(paging) {

    	let pageHtml = "";
    	pageHtml += "<ul class='pagination'>";

    	//first
    	const first = parseInt(paging.firstPage);
    	pageHtml += "<li class='paginate_button page-item first'>";

    	if (paging.currentPage === paging.firstPage) {
    		pageHtml += "<button class='page-link' disabled>";
    	} else {
    		pageHtml += "<button class='page-link' style='cursor: pointer;' onclick='movePage("+ first +")'>";
    	}
    	pageHtml += "<span class='hidden'>first page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	//prev
    	const prev = parseInt(paging.currentPage) -1;
    	pageHtml += "<li class='paginate_button page-item prev'>";

    	if (paging.firstPage === paging.currentPage) {
    		pageHtml += "<button class='page-link' disabled>";
    	} else {
    		pageHtml += "<button class='page-link' style='cursor: pointer;' onclick='movePage("+ prev +")'>";
    	}
    	pageHtml += "<span class='hidden'>prev page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>";

    	//① ~ ⑩
    	for (let i = paging.firstPageNo; i <= paging.lastPageNo; i++) {
    		pageHtml += "<li class='paginate_button page-item";
    		//현재 페이지가 인덱스와 같으면 active class 추가
    		if (paging.currentPage === i) {
    			pageHtml += " active'>";
    			pageHtml += "<button class='page-link' style='cursor: pointer;' onclick='movePage("+ i +")'>";
    			pageHtml += i;
    			pageHtml += "</button>";
    			pageHtml += "</li>"
    		} else {
    			pageHtml += "'>";
    			pageHtml += "<button class='page-link' style='cursor: pointer;' onclick='movePage("+ i +")'>";
    			pageHtml += i;
    			pageHtml += "</button>";
    			pageHtml += "</li>"
    		}
    	}

    	//next
    	const next = parseInt(paging.currentPage) + 1;
    	pageHtml += "<li class='paginate_button page-item next'>";

    	if (paging.currentPage === paging.lastPage) {
    		pageHtml += "<button class='page-link' disabled>";
    	} else {
    		pageHtml += "<button class='page-link' style='cursor: pointer;' onclick='movePage("+ next +")'>";
    	}
    	pageHtml += "<span class='hidden'>next page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	//last
    	const last = parseInt(paging.lastPage);
    	pageHtml += "<li class='paginate_button page-item last'>";

    	if (paging.currentPage === paging.lastPage) {
    		pageHtml += "<button class='page-link' disabled>";
    	} else {
    		pageHtml += "<button class='page-link' style='cursor: pointer;' onclick='movePage("+ last +")'>";
    	}
    	pageHtml += "<span class='hidden'>last page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	pageHtml += "</ul>";
    	//페이지네이션 영역에 반영
    	$(".tbl-paging").html(pageHtml);
    }

after source 😎

/**
     * 페이지네이션을 그린다.
     * @param paging
     */
    function drawPagination(paging) {

    	let pageHtml = "";
    	pageHtml += "<ul class='pagination'>";

    	//first
    	const first = parseInt(paging.firstPage);
    	pageHtml += "<li class='paginate_button page-item first'>";

    	if (paging.currentPage === paging.firstPage) {
    		pageHtml += "<button type='button' class='page-link' disabled>";
    	} else {
    		pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ first +")'>";
    	}
    	pageHtml += "<span class='hidden'>first page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	//prev
    	const prev = parseInt(paging.currentPage) -1;
    	pageHtml += "<li class='paginate_button page-item prev'>";

    	if (paging.firstPage === paging.currentPage) {
    		pageHtml += "<button type='button'  class='page-link' disabled>";
    	} else {
    		pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ prev +")'>";
    	}
    	pageHtml += "<span class='hidden'>prev page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>";

    	//① ~ ⑩
    	for (let i = paging.firstPageNo; i <= paging.lastPageNo; i++) {
    		pageHtml += "<li class='paginate_button page-item";
    		//현재 페이지가 인덱스와 같으면 active class 추가
    		if (paging.currentPage === i) {
    			pageHtml += " active'>";
    			pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ i +")'>";
    			pageHtml += i;
    			pageHtml += "</button>";
    			pageHtml += "</li>"
    		} else {
    			pageHtml += "'>";
    			pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ i +")'>";
    			pageHtml += i;
    			pageHtml += "</button>";
    			pageHtml += "</li>"
    		}
    	}

    	//next
    	const next = parseInt(paging.currentPage) + 1;
    	pageHtml += "<li class='paginate_button page-item next'>";

    	if (paging.currentPage === paging.lastPage) {
    		pageHtml += "<button type='button'  class='page-link' disabled>";
    	} else {
    		pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ next +")'>";
    	}
    	pageHtml += "<span class='hidden'>next page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	//last
    	const last = parseInt(paging.lastPage);
    	pageHtml += "<li class='paginate_button page-item last'>";

    	if (paging.currentPage === paging.lastPage) {
    		pageHtml += "<button type='button'  class='page-link' disabled>";
    	} else {
    		pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ last +")'>";
    	}
    	pageHtml += "<span class='hidden'>last page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	pageHtml += "</ul>";
    	//페이지네이션 영역에 반영
    	$(".tbl-paging").html(pageHtml);
    }

달라진 부분은 동적으로 만든 button에 type을 button으로 설정했다.

이번에 알게된 사실인데 button에 type을 명시하지 않을 경우 default값은 submit이라고 한다.

그러니 버튼을 누를 때마다 ajax로 2페이지를 가져온 다음 form submit을 한번 더 하게 된거다.

form action에 아무값도 주지 않았기에 현재 페이지로 submit을 태워서 계속 같은 페이지가 로드 된 거다.

사실을 알게 된 후 form action에 존재하지 않는 주소를 줬더니 예상대로 404 에러가 발생했다.

이 삽질을 통해 느낀 교훈: button을 만들 때 명시적으로 항상 type을 선언하도록 하자.

전체 소스(샘플용)

리팩토링 X

<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-latest.min.js"></script>
<style>
    li {
        list-style: none;
        float: left;
    }
</style>
</head>
<body>
<form id="searchForm">
    <select name="type" id="type">
        <option value="">전체</option>
        <option value="T">제목</option>
        <option value="C">내용</option>
        <option value="W">작성자</option>
    </select>
    <input type="text" name="keyword" id="keyword">
    <button id="searchBtn">검색</button>
    <a th:href="@{/board/reg}">글쓰기</a>
    <div id="count"></div>
<table>
    <thead>
        <th>번호</th>
        <th>제목</th>
        <th>내용</th>
        <th>작성자</th>
        <th>등록일</th>
    </thead>
    <tbody id="boardBody"></tbody>
</table>
    <div class="tbl-paging"></div>
</form>

<script th:inline="javascript" type="text/javascript">
    //구분
    const menu = "board";
    //검색조건
    let searchData = "";

    $(function () {

        getList("", 1, menu);
    });

    //검색
    $("#searchBtn").on("click", function () {
        searchData = $('#searchForm').serialize();
        getList(searchData, 1, menu);
    });

    /**
     * 페이지 이동
     * @param currentPage
     */
    function movePage(currentPage){
    	//검색조건이 있을 경우에만 검색조건 추가(검색조건 유지한 채 페이지 이동)
    	(searchData != null) ? getList(searchData, currentPage, menu) : getList("", currentPage, menu);
    }

    /**
     * ajax 통신으로 리스트를 조회한다.
     * @param searchParam 검색조건
     * @param currentPage 현재 페이지
     * @param type 메뉴타입
     */
    function getList(searchParam, currentPage, type){
        console.log("getList!!!!");
    	$.ajax({
    		url: "/api/" + type +"?currentPage=" + currentPage,
    		type:"get",
    		//IE 브라우저 사용 안한다는 가정하에 주석처리
    		// cache: false,
    		data: searchParam,
    		success: function (data){
    			//draw tbody
    			drawTbody(data, type);
    			//draw pagination
    			drawPagination(data.paging);
    		},
    		error:function(e){
    		}
    	});
    }

    /**
     * tbody에 html을 덮어쓴다.
     * @param data
     * @param gubun
     */
    function drawTbody(data, gubun) {
        //drawHtml, draw 대상 tbody
        let htmlData, targetTbody = "";
        targetTbody = "boardBody";

        // foreach start
        for (let i = 0; i < data.list.length; i++) {
            htmlData += "<tr ";
            htmlData += "data-sno=" + data.list[i].boardSno + ">";
            htmlData += "<td>";
            htmlData += data.list[i].no;
            htmlData += "</td>";
            htmlData += "<td>";
            htmlData += "<a href='#' onclick='detail(\"" + data.list[i].boardSno + "\")'>";
            htmlData += data.list[i].title;
            htmlData += "</a>";
            htmlData += "</td>";
            htmlData += "<td>";
            htmlData += data.list[i].content;
            htmlData += "</td>";
            htmlData += "<td>";
            htmlData += data.list[i].userId;
            htmlData += "</td>";
            htmlData += "<td>";
            htmlData += data.list[i].regDate;
            htmlData += "</td>";
            htmlData += "<td>";
            htmlData += "<a href='#' onclick='delBoard(\"" + data.list[i].boardSno + "\")'>삭제</a>";
            htmlData += "</td>";
            htmlData += "</tr>";
        }
        // foreach end
        //tbody에 반영
        $("#" + targetTbody + "").html(htmlData);
        countHtml = "";
        countHtml += data.count + "건";
        $("#count").text(countHtml);
    }
    // end function getList()

    //상세보기
    function detail(sno) {
        location.href = "/board/mod/" + sno;
    }

    function delBoard(sno) {
        $.ajax({
            url: "/api/board/" + sno,
            type:"delete",
            //IE 브라우저 사용 안한다는 가정하에 주석처리
            // cache: false,
            success: function (data) {
                if(data.code === "1") alert("삭제 되었습니다.");
                location.href = "/board"
            },
            error:function(e){
                console.log(e);
            }
        });
    }

    /**
     * 페이지네이션을 그린다.
     * @param paging
     */
    function drawPagination(paging) {

    	let pageHtml = "";
    	pageHtml += "<ul class='pagination'>";

    	//first
    	const first = parseInt(paging.firstPage);
    	pageHtml += "<li class='paginate_button page-item first'>";

    	if (paging.currentPage === paging.firstPage) {
    		pageHtml += "<button type='button' class='page-link' disabled>";
    	} else {
    		pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ first +")'>";
    	}
    	pageHtml += "<span class='hidden'>first page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	//prev
    	const prev = parseInt(paging.currentPage) -1;
    	pageHtml += "<li class='paginate_button page-item prev'>";

    	if (paging.firstPage === paging.currentPage) {
    		pageHtml += "<button type='button'  class='page-link' disabled>";
    	} else {
    		pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ prev +")'>";
    	}
    	pageHtml += "<span class='hidden'>prev page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>";

    	//① ~ ⑩
    	for (let i = paging.firstPageNo; i <= paging.lastPageNo; i++) {
    		pageHtml += "<li class='paginate_button page-item";
    		//현재 페이지가 인덱스와 같으면 active class 추가
    		if (paging.currentPage === i) {
    			pageHtml += " active'>";
    			pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ i +")'>";
    			pageHtml += i;
    			pageHtml += "</button>";
    			pageHtml += "</li>"
    		} else {
    			pageHtml += "'>";
    			pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ i +")'>";
    			pageHtml += i;
    			pageHtml += "</button>";
    			pageHtml += "</li>"
    		}
    	}

    	//next
    	const next = parseInt(paging.currentPage) + 1;
    	pageHtml += "<li class='paginate_button page-item next'>";

    	if (paging.currentPage === paging.lastPage) {
    		pageHtml += "<button type='button'  class='page-link' disabled>";
    	} else {
    		pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ next +")'>";
    	}
    	pageHtml += "<span class='hidden'>next page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	//last
    	const last = parseInt(paging.lastPage);
    	pageHtml += "<li class='paginate_button page-item last'>";

    	if (paging.currentPage === paging.lastPage) {
    		pageHtml += "<button type='button'  class='page-link' disabled>";
    	} else {
    		pageHtml += "<button type='button'  class='page-link' style='cursor: pointer;' onclick='movePage("+ last +")'>";
    	}
    	pageHtml += "<span class='hidden'>last page</span>";
    	pageHtml += "</button>";
    	pageHtml += "</li>"

    	pageHtml += "</ul>";
    	//페이지네이션 영역에 반영
    	$(".tbl-paging").html(pageHtml);
    }


</script>

</body>
</html>

개인 스터디 기록을 메모하는 공간이라 틀린점이 있을 수 있습니다.

틀린 점 있을 경우 댓글 부탁드립니다.

'IT > development' 카테고리의 다른 글

[ChatGPT] ChatGPT 고맙다.  (0) 2023.07.22
[React.js] state 사용법  (0) 2023.07.13
[dbeaver] dbeaver DDL, DML 생성  (0) 2023.07.08
[JavaScript] 동적 엘리먼트 onclick 문자열  (0) 2023.07.05
[JavaScript] validation  (0) 2023.06.25