참고 : https://www.w3schools.com/bootstrap5/bootstrap_navbar.php
<div class="container">
안에
<form class="d-flex">
<input class="form-control me-2" type="text" placeholder="Search">
<button class="btn btn-primary" type="button">Search</button>
</form>
를 넣자. container
class가 화면의 70~80%를 차지하는 반응형인데, container
밖에 폼을 넣으면 반응형 적용이 안 된다.
input태그(inline-block)임에도 서치 칸이 길쭉한 이유는 form-control
이 block속성이라 그렇다.
flex는 fixed나 absolute와 다르게 자기 부모의 크기를 벗어날 수 없으므로 form을 감싸는 박스를 작게 만들어 크기를 지정할 수 있다.
<div style="width: 300px">
<form class="d-flex">
<input class="form-control me-2" type="text" placeholder="Search">
<button class="btn btn-primary" type="button">Search</button>
</form>
</div>
오른쪽 배치를 위해 <div>
를 하나 더 만들어 flex적용.
button 타입도 submit으로 수정
<br/>
<div class="d-flex justify-content-end">
<div style="width: 300px">
<form class="d-flex">
<input class="form-control me-2" type="text" placeholder="Search">
<button class="btn btn-primary" type="submit">Search</button>
</form>
</div>
</div>
select할 것이므로 GET 요청이다.
<form class="d-flex" method="get" action="/keyword=?">
쿼리스트링의 경우 form태그에서 사용시 주소에 적지 않아도 된다.
form태그의 input타입의 name에 걸면 된다. GET요청이기 때문에 데이터를 주소에 들고 가기 때문이다. (localhost:8000/?keyword=사용자입력값
이 되는 것)
<form class="d-flex" method="get" action="/">
<input class="form-control me-2" type="text" placeholder="Search" name="keyword">
<button class="btn btn-primary" type="button">Search</button>
</form>
이제 페이지 주소는 이런 형태가 된다. ?page=0&keyword=스프링
검색을 할 경우 주소에 page값은 넘기지 않게 되므로 if문에 의해 page는 디폴트값 0이 된다. (받고싶으면 main.jsp에 <input type="hidden" value="0" name="page"/>
를 넣으면 된다.)
keyword가 null || keyword.isEmpty()
일 때와 그렇지 않을 때 실행되는 DAO가 달라야 한다.
@Override public boolean isEmpty() { return value.length == 0; }
- 특징 : " "(공백)을 blank(공백인정X)로 보지 않고 비어있지 않다고 본다.
StringUtils.isEmpty("") = true
StringUtils.isEmpty(" ") = false
// 1번째 ?page=0&keyword=스프링
// 검색을 하면 page 값을 안 넘기므로 디폴트값 0이 들어옴
@GetMapping({ "/", "/boards" })
public String getBoardList(Model model, Integer page, String keyword) {
if (page == null) {
page = 0;
}
Integer startNum = page * 3;
if (keyword == null || keyword.isEmpty()) {
List<MainView> boardsList = boardsDao.findAll(startNum);
PagingView paging = boardsDao.paging(page);
// DB에서 쿼리를 만들어 넣어주기 힘드니까 Java에서 나머지 변수들을 설정
paging.makeBlockInfo();
model.addAttribute("boardsList", boardsList);
model.addAttribute("paging", paging);
return "boards/main";
} else {
}
}
else문은 동적쿼리를 만든 뒤 작성해보겠다.
keyword가 !=null
이면 Search를 한 것이므로
findAll 쿼리와 paging 쿼리를 수정해주어야 한다.
[ 방법은 2가지 : 1. 쿼리 하나 더 만들기 2. 동적 쿼리 사용]
1. findAll쿼리는 새 쿼리를 만들어서 적용해보고
2. paging은 동적쿼리를 사용해 적용해보겠다.
<select id="findSearch"
resultType="site.metacoding.red.domain.boards.mapper.MainView">
SELECT b.id, b.title, u.username
FROM boards b
INNER JOIN
users u
ON b.usersId = u.id
WHERE title like '%'||#{keyword}||'%'
ORDER BY b.id DESC
OFFSET #{startNum} ROWS
FETCH NEXT 3 ROWS ONLY
</select>
<select id="paging"
resultType="site.metacoding.red.domain.boards.mapper.PagingView">
SELECT totalCount,
totalPage,
currentPage,
decode(currentPage, 0, 1, 0) first,
decode(currentPage, totalPage-1, 1,
0) last
FROM
(
select count(*) totalCount, ceil(count(*)/3) totalPage,
#{page} currentPage, 0
first, 0 last
FROM boards
<if test="keyword != null">
WHERE title like '%'||#{keyword}||'%'
</if>
)
</select>
@GetMapping({ "/", "/boards" })
public String getBoardList(Model model, Integer page, String keyword) {
if (page == null) {
page = 0;
}
Integer startNum = page * 3;
if (keyword == null || keyword.isEmpty() ) {
List<MainView> boardsList = boardsDao.findAll(startNum);
PagingView paging = boardsDao.paging(page, null);
//키워드는 무조건 받게 되어 있으므로 keyword가 없을 땐 null이라도 받게 해줘야 함
// DB에서 쿼리를 만들어 넣어주기 힘드니까 Java에서 나머지 변수들을 설정
paging.makeBlockInfo();
model.addAttribute("boardsList", boardsList);
model.addAttribute("paging", paging);
} else {
List<MainView> boardsList = boardsDao.findSearch(startNum, keyword);
PagingView paging = boardsDao.paging(page, keyword); // 또는 keyword대신 null 넣기
// DB에서 쿼리를 만들어 넣어주기 힘드니까 Java에서 나머지 변수들을 설정
paging.makeBlockInfo();
model.addAttribute("boardsList", boardsList);
model.addAttribute("paging", paging);
}
return "boards/main";
}
파라미터가 두개일 경우 Mybatis에서 인식을 하지 못하기 때문에 @param을 사용한다.
@Param 어노테이션을 붙이면 본인이 원하는 명으로 mapper에서 사용할 수 있다.
public interface BoardsDao {
public PagingDto paging(@Param("page") Integer page, @Param("keyword") String keyword);
public void insert(Boards boards);
public Boards findById(Integer id);
public List<MainDto> findAll(int startNum);
public List<MainDto> findSearch(@Param("startNum") int startNum, @Param("keyword") String keyword);
public void update(Boards boards);
public void delete(Integer id);
}
<ul class="pagination">
<li class='page-item ${paging.first ? "disabled" : ""}'><a class="page-link"
href="?page=${paging.currentPage-1}&keyword=${paging.keyword}">Prev</a></li>
<c:forEach var="num" begin="${paging.startPageNum}" end="${paging.lastPageNum}" step="1">
<li class='page-item ${paging.currentPage == num-1 ? "active" : ""}'><a class="page-link"
href="?page=${num-1}&keyword=${paging.keyword}">${num}</a></li>
</c:forEach>
<li class='page-item ${paging.last ? "disabled" : ""}'><a class="page-link"
href="?page=${paging.currentPage+1}&keyword=${paging.keyword}">Next</a></li>
</ul>
❓❗️그런데 이 경우 keyword로 검색은 되나 다음 페이지로 넘어갈 경우 keyword값이
keyword=
로 null이 되어 전체검색 페이지로 넘어간다.
🔔
PagingView paging = boardsDao.paging(page, keyword);
에서paging
에 keyword가 담겨있지 않기 때문이다. (DB에서 keyword가 select되지 않기 때문) Mapper는 작동되어 keyword로 검색한 페이지가 나오지만 <-> 검색된 페이지의 Page Bar에는 Model에 담겨 전송될 데이터 중 keyword값이 없어keyword=
값으로 나오는 것이다.
💻 따라서 PagingView에 keyword 변수를 만들어주고,makeBlockInfo()
에keyword
값을 직접 받아paging
에keyword
값이 생기도록 한 뒤, Model로 View에 전달하도록 해주어야 한다.
package site.metacoding.red.domain.boards.mapper;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class PagingView {
private String keyword;
private Integer currentBlock; // 변수
private Integer blockCount; // 상수 / 한 블락의 페이지 넘버 수 개수(5) 1-5, 6-10
private Integer startPageNum; // 변수 1 -> 6 -> 11
private Integer lastPageNum; // 변수 5 -> 10 -> 15
private Integer startNum;
private Integer totalCount;
private Integer totalPage;
private Integer currentPage;
private boolean isLast; // getter가 만들어지면 getisLast()가 아닌 isLast() 이름으로 만들어짐 -> el표현식에서는 last로 찾아짐
private boolean isFirst; // getter가 만들어지면 getisFirst()가 아닌isFirst() 이름으로 만들어짐 -> el표현식에서는 last로 찾아짐
public void makeBlockInfo(String keyword) {
this.keyword = keyword;
this.blockCount = 5;
this.currentBlock = currentPage / blockCount;
this.startPageNum = 1 + blockCount * currentBlock;
this.lastPageNum = (startPageNum + blockCount) -1;
if (totalPage < lastPageNum) {
this.lastPageNum = totalPage;
}
}
}
@GetMapping({ "/", "/boards" })
public String getBoardList(Model model, Integer page, String keyword) {
if (page == null) {
page = 0;
}
Integer startNum = page * 3;
if (keyword == null || keyword.isEmpty() ) {
List<MainView> boardsList = boardsDao.findAll(startNum);
PagingView paging = boardsDao.paging(page, null);
//키워드는 무조건 받게 되어 있으므로 keyword가 없을 땐 null이라도 받게 해줘야 함
// DB에서 쿼리를 만들어 넣어주기 힘드니까 Java에서 나머지 변수들을 설정
paging.makeBlockInfo();
model.addAttribute("boardsList", boardsList);
model.addAttribute("paging", paging);
} else {
List<MainView> boardsList = boardsDao.findSearch(startNum, keyword);
PagingView paging = boardsDao.paging(page, keyword); // 또는 keyword대신 null 넣기
// DB에서 쿼리를 만들어 넣어주기 힘드니까 Java에서 나머지 변수들을 설정
paging.makeBlockInfo();
model.addAttribute("boardsList", boardsList);
model.addAttribute("paging", paging);
}
return "boards/main";
}