개발
페이지네이션과 로드 모어(Load More)를 동시 구현해보자
31 views as of January 14, 2025.
우리가 일반적으로 여러 데이터 레코드를 보기좋게 구현하기 위해서는 테이블 구조를 이용하곤한다.
테이블 구조는 행과 열로 구분되어있어 행은 레코드를 나타내고 열은 데이터의 속성을 나타내는 지표로써 데이터가 체계적으로 정리된 구조를 가지고 있어 표를 보는법이 익숙한 현대인들은 곧잘 이용하곤한다.
그리고 레코드값이 이미지를 가진다면 '카드 리스트'라는 방식으로 이런 데이터를 표기하기도 한다.
뭐 당장 이메일만 들어가도 이런 리스트형식의 데이터 레코드를 확인할 수 있다.
그리고 이런 데이터 레코드가 일정 수치가 넘어가면 이를 한화면에 보여주기가 성능면으로도 시각적으로도 안좋으므로 대부분의 테이블은 페이지네이션(Pagination)이라 불리우는 페이징 기능을 통해 여러 데이터 레코드에 대한 접근을 가능케 한다.
하지만 이런 고전적인 기능이 좀 싫은 사람들은 동적으로 새로운 레코드를 가져와서 보이게 하는 다른 방식을 취급하는데, '로드 모어(더보기)'기능과 '인피티니 스크롤(무한스크롤)'이 그 좋은 예이다.
위 두가지 기능은 로직은 같은걸 이용하지만 로드 모어는 사용자의 요청을 기준으로 새 데이터를 가져와 보여주는것이고, 인피티니 스크롤은 사용자의 의지가 개입되지않고 자연스럽게 새 데이터를 가져와 사용자게에 노출시켜주는 것이다.
이런기능은 인스타그램이나 유튜브 숏츠등, 사용자에게 연속적인 컨텐츠를 제공하기위한 곳에서 주로 쓰고있다.
페이지네이션과 로드 모어의 동시 구현?
자 그럼 위에서 언급한 페이징과 더보기 기능을 같이 구현해보는건 어떨까?
보통은 2개 중 하나만 구현하지만 각자 기능의 장단점이 분명히 존재한다.
페이지네이션 | 로드 모어 | |
---|---|---|
전체 데이터 | 조회 가능 | 조회 불가 |
처리방식 | 페이지 이동 | 비동기 처리 |
페이징은 모든 페이지에 바로 접근할 수 있는 표지(네비게이션 링크)를 가지고있으나 더보기 기능은 무조건 다음 페이지(최신기준으로 과거)에 해당하는 데이터만 가져올 수 있다.
그러나 더보기 기능은 별도의 페이지 이동이 발생하지않게 비동기 방식으로 동적 컨텐츠 제어로 사용자에게 좋은 사용감을 전달할 수 있다.
나는 최근에 카드 리스트의 데이터를 나열하는 과정에서 페이징 기능을 붙이자니 매번 페이지 새로고침이 되는게 부담스럽고, 더보기 기능을 붙이자니 내가 원하는 시간의 레코드를 찾는게 너무 어려웠다.
문득 이 두가지 기능이 한번에 다 지원되면 좋겠다고 생각했고 더보기 기능에 페이지네이션의
page
변수를 이용한 방식으로 개조해 이 두가지 방식을 전부 통틀어서 사용가능하게 개조, 구현해보기로 했다.page 변수를 이용한 더보기 기능 구현
선행 작업: 페이지네이션 구현
라라벨의 경우에는 페이지네이션을 아래와 같은 방식으로 백단에서 요청할 수 있다.
$posts = Post::orderByDesc('created_at')->paginate()->appends($request->input());
Copy
그러면 앞단에서 아래와 같은 블레이드 기능을 이용해서 쉽게 페이지네이션을 구현할 수 있다.
<div class="wrap_pagination">
{{ $posts->onEachSide(2)->links() }}
</div>
Copy
이때 페이지네이션은 기본적으로 url에
page
라는 GET 변수를 통해 백단과 페이지네이션 기능을 연동한다.이 기능 자체는 흠잡을데 없이 잘 구현이 되어있고, 나는 여기서 이 page 변수를 더보기 기능의 로직에 통합 시키면 된다고 생각했다.
백단 코드
그래서 아래와 같이 더보기 요청이 올때 백단에서 현재 page 변수의 값을 기준으로 데이터를 잘라내서 페이지 리스트를 랜더링 하고 이 데이터를 반환하는 방식으로 메소드를 추가 구현해보았다.
public function index(Request $request)
{
$posts = Post::query();
$keyword = $request->input('keyword');
if($keyword != null) {
$posts = $posts->where('title', 'LIKE', "%{$keyword}%");
}
$posts = $posts->orderByDesc('created_at')->paginate()->appends($request->input());
return view('admin.post.index', compact('posts'));
}
public function indexLoad(Request $request)
{
try {
$page = $request->input('page', 1);
$perPage = $this->perPage;
$total = Post::count();
// 요청된 페이지 번호가 유효한지 검증
$maxPages = ceil($total / $perPage);
if ($page > $maxPages) {
return response()->json(['message' => 'No more data', 'lastPage' => true], 404);
}
$posts = Post::orderBy('created_at', 'desc')->paginate($perPage, ['*'], 'page', $page);
$isLastPage = $page >= $maxPages;
return response()->json([
'html' => view('admin.post._rows', compact('posts'))->render(),
'lastPage' => $isLastPage
]);
} catch (\Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
}
Copy
순서대로
index
메소드는 기존의 리스트 페이지를 보여주는 기본 메소드고 indexLoad
메소드가 더보기를 위해 추가 구현한 메소드이다.indexLoad
메소드는 요청된 데이터에서 page
변수를 찾아 이 변수에 해당하는 데이터를 모델에서 조회하고 이를 뷰파일과 함께 랜더해 반환하는 역할을 한다.앞단 코드
실제로 더보기 기능은 백단보다 앞단이 훨씬 복잡한 기능이므로 앞단 코드도 한번 보자.
<div class="wrap_pagination">
<button id="btnLoad" class="btn btn_load mr-3" onclick="loadData()">Read More</button>
{{ $posts->onEachSide(2)->links() }}
</div>
<script>
let currentPage = '{{ request('page', 1) }}';
async function loadData() {
currentPage++;
const btnLoad = document.getElementById('btnLoad');
btnLoad.disabled = true;
try {
const response = await fetch(`/post/load?page=${currentPage}`);
if (!response.ok) {
if (response.status === 404) {
btnLoad.style.display = 'none';
} else {
console.error('Error loading data:', response.statusText);
btnLoad.disabled = false;
}
return;
}
const data = await response.json();
if (data.html.trim() !== '') {
document.getElementById('listPost').insertAdjacentHTML('beforeend', data.html);
if (data.lastPage) {
btnLoad.style.display = 'none';
} else {
btnLoad.disabled = false;
}
} else {
btnLoad.style.display = 'none';
}
} catch (error) {
console.error('Error loading data:', error);
btnLoad.disabled = false;
}
}
</script>
Copy
현재 페이지가 로드되었을때 자바 스크립트 변수
currentPage
에 현재 페이지값을 기록해둔다.그리고 더보기 버튼이 클릭시
currentPage++
처리를 해 다음 페이지 정보를 백단에 요청하고 이 데이터를 기존 리스트 데이터의 위치(#listPost
)에 넣어주면된다.위 코드에는 추가적으로
btnLoad
에 대한 각종 예외처리 코드가 담겨있는데, 기존 더보기 로직이 실행중일때 추가 실행을 막기위한 버튼의 disabled
제어 처리가 추가되어있다.작동 예시
이렇게 구현된 코드는 아래와 같이 작동된다.
페이징으로 보였던 2페이지가 'Road More' 버튼을 통해 아래에 추가 랜더링된것을 확인할 수 있다.
결론
이런식의 이중 페이징구현은 사실 목적에 맞게 둘중 하나만 구현하는게 맞긴한데, 필요에 따라 2개가 다 필요한 경우도 존재한다는걸 최근에 카드 리스트를 만들면서 알게되었다.
카드 리스트는 대량이미지와함께 랜더링되므로 페이징을 적용해버리면 매번 페이지가 과도하게 로드되면서 사용감이 워낙 안좋은게 도드라졌고, 이를 위해 더보기 기능으로 바꾸니 예전 데이터에 대한 접근이 매우 힘든경우가 발생했기 때문이다.
결국 이런 기능은 뭐가 맞다기 보다 필요한 목적에 따라 충분히 결합될 수 도 있다고 생각이 드는 작업이었다.
#Laravel #php
개발 카테고리의 다른 글
01/10
S3 데이터 가져와서 테이블로 관리하기
S3 서버는 아마존 웹 서비스에서 이용할 수 있는 스토리지 저장 서비스이다.실제로 대용량의 파일을 저장해도 과금이 별로 안되...
01/07
Flex Box 사용시 자식 요소의 높이가 늘어나는 문제 해결 방법
문제퍼블리싱 코드를 이리저리 만지다가 특이한 현상을 발견했다. 크아악!! 내 badge의 높이 상태가? 위 스샷처럼 타이틀 영역에...
01/02
구글 애드센스 모바일 반응형 광고 높이 조정하기
본 글은 구글 애드센스의 반응형 광고를 달때 로딩되는 광고가 원하는 크기보다 너무 큰 광고가 로딩될때 이를 해결하기위한 내...
많은 인사이트를 얻게되는 것 같네요~ 앞으로도 좋은 포스팅 부탁합니다^^
이런거보면 결국 기능 목적에 따라 구현은 정말 무한대로 할 수 있는거 같습니다~