제작중인 블로그에 무한 스크롤을 적용할 필요가 있었다. 무한 스크롤은 사용자가 필요한 만큼만 리소스를 요청하고 적용하기도 간단하다. 이 블로그에도 무한 스크롤을 적용했는데 그래서 더 무한 스크롤에 집착이 생긴다. 너무 좋은 기능이다. 여하지간 무한 스크롤을 사용하기 위해선 Pagination이 되어 있어야 하는데 장고에서 페이지네이션을 구현하는 것이 매우 간단했다.


views.py

우선 페이지 네이션을 구현하기 위해선 아래 라이브러리를 import 해주어야 한다.

1
from django.core.paginator import Paginator

페이지 네이터를 적용하기 전 코드

1
2
3
def post_list(request):
    posts = Post.objects.filter(created_date__lte=timezone.now()).order_by('created_date').reverse()
    return render(request, 'board/post_list.html',{ 'posts':posts })

페이지 네이터를 적용한 후 코드

1
2
3
4
5
6
def post_list(request):
    posts = Post.objects.filter(created_date__lte=timezone.now()).order_by('created_date').reverse()
    paginator = Paginator(posts, 10)
    page = request.GET.get('page')
    pageposts = paginator.get_page(page)
    return render(request, 'board/post_list.html',{ 'posts':posts,'pageposts':pageposts })

위와같이 pagenator에서 필터를 적용한 오브젝트와 페이지를 분할할 갯수를 선정한다. 이것을 pageposts로 넘겨주면 설정한 10개 만큼의 포스트만 출력된다. 이후 사이트에서 ?page=N으로 페이지를 넘길 수 있다.

1
2
3
{% for post in pageposts%}
    ...
{% endfor %}

그리곤 탬플릿에서 위와같이 pageposts로 넘겨주면 알아서 필요한 정보를 생성해준다.


Infinite Scroll

깃허브 블로그에 적용하려고 찾았던 소스코드인데 어디서 구했는지 기억이 안난다… 찾으면 반드시 출처를 적도록 할 예정이다. 여하지간 해당 코드에서 지금 사용하고있는 url에 맞는 구조에 소스를 변경하였다. 또한 이유는 모르겠지만 장고의 경우 최대의 페이지를 넘어가도 계속 포스트가 넘어와서 소스코드를 수정하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class InfiniteScroll {
    constructor (path, wrapperId, rastPage) {
        if (path === undefined || wrapperId === undefined) throw Error ('no parameter.');
        this.path = path;
        this.pNum = 2;
        this.wNode = document.getElementById(wrapperId);
        this.wrapperId = wrapperId;
        this.rastPage = rastPage;
        this.enable = true;

        this.detectScroll();
    }

    detectScroll() {
        window.onscroll = (ev) => {
            if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight && this.pNum <= this.rastPage) 
                this.getNewPost();
        };    
    }
    getNewPost() {
        if (this.enable === false) return false;
        this.enable = false;
        const xmlhttp = new XMLHttpRequest();
        xmlhttp.onreadystatechange = () => {
            if (xmlhttp.readyState == XMLHttpRequest.DONE) {
                if (xmlhttp.status == 200) {
                    this.pNum++;
                    const childItems = this.getChildItemsByAjaxHTML(xmlhttp.responseText);
                    this.appendNewItems(childItems);
                }
                return this.enable = true;
            }
        }
        xmlhttp.open("GET", `${location.origin + this.path + this.pNum}`, true);
        xmlhttp.send();
    }

    getChildItemsByAjaxHTML(HTMLText) {
        const newHTML = document.createElement('html');
        newHTML.innerHTML = HTMLText;
        const childItems = newHTML.querySelectorAll(`#${this.wrapperId} > *`);
        return childItems;
    }

    appendNewItems(items) {
        items.forEach(item => {
            this.wNode.appendChild(item);
        });
    }
}

위 자바스크립트 파일을 자신의 static 경로에 저장한다. 그리고 목록이 출력되는 index 템플릿 파일에 저장한 스크립트와 아래 스크립트를 추가한다. 아래의 스크립트는 3개의 매개 변수를 넘겨줘야 한다. 3번째 매개변수는 필자가 추가한 파라미터로 오류를 수정하기 위한 변수다.

1
2
3
4
5
6
7
<script src="{{ static_url }}/assets/js/InfiniteScroll.js"></script>
<script type="text/javascript">
var postWrapperId = 'card-wrapper';
var paginatePath = '?page=';
var lastPage = {{ pageposts.paginator.num_pages }};
new InfiniteScroll(paginatePath, postWrapperId, lastPage);
</script>

또 여기서 중요한 점은 포스트 목록이 출력되는 부분을 태그 하나로 감싸서 태그에 적혀진 postWrapperId와 같은 아이디를 넣어줘야 한다.

1
2
3
4
5
<div id="card-wrapper">
    {% for post in pageposts%}
        ...
    {% endfor %}
</div>

위 코드는 하나의 예시이다.


결과물

좀 더 미디엄스럽게 변했다.

WRITTEN BY

배진오

소비적인 일보단 생산적인 일을 추구하며, 좋아하는 일을 잘하고 싶어합니다 :D
im@baejino.com

comments powered by Disqus