() translation by (you can also view the original English article)
이 연재 기사의 4부에서는 버킷 리스트 애플리케이션의 희망사항 Edit
및 Delete
기능을 구현하는 방법을 살펴봤습니다. 이번 5부에서는 사용자 홈 리스트의 페이징 기능을 구현하겠습니다.
시작하기
먼저 깃허브(GitHub)에서 튜토리얼의 이전 내용을 복제합니다.
1 |
git clone https://github.com/jay3dec/PythonFlaskMySQLApp_Part4.git |
소스코드가 복제되면 프로젝트 디렉터리로 이동한 후 웹 서버를 구동합니다.
1 |
cd PythonFlaskMySQLApp_Part4
|
2 |
python app.py |
브라우저에서 http://localhost:5002/로 이동하면 실행 중인 애플리케이션을 볼 수 있을 것입니다.
페이징 구현하기
사용자 홈 페이지의 희망사항 목록이 늘어나면 페이지 아래로 스크롤됩니다. 따라서 페이징을 구현하는 것이 중요합니다. 여기서는 페이지에 표시되는 항목의 수를 특정 숫자로 제한할 것입니다.
희망사항 조회 프로시저 수정
먼저 sp_GetWishByUser
프로시저에서 limit
및 offset
값을 기반으로 한 결과를 반환하도록 수정하겠습니다. 이번에는 저장 프로시저 문장을 동적으로 생성해서 limit 및 offset 값을 기반으로 결과 집합을 반환하겠습니다. 다음은 수정된 sp_GetWishByUser
MySQL 저장 프로시저입니다.
1 |
USE `BucketList`; |
2 |
DROP procedure IF EXISTS `sp_GetWishByUser`; |
3 |
|
4 |
DELIMITER $$ |
5 |
USE `BucketList`$$ |
6 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetWishByUser`( |
7 |
IN p_user_id bigint, |
8 |
IN p_limit int, |
9 |
IN p_offset int |
10 |
)
|
11 |
BEGIN
|
12 |
SET @t1 = CONCAT( 'select * from tbl_wish where wish_user_id = ', p_user_id, ' order by wish_date desc limit ',p_limit,' offset ',p_offset); |
13 |
PREPARE stmt FROM @t1; |
14 |
EXECUTE stmt; |
15 |
DEALLOCATE PREPARE stmt1; |
16 |
END$$ |
17 |
|
18 |
DELIMITER ; |
19 |
위의 저장 프로시저에서 볼 수 있듯이 여기서는 동적 SQL 쿼리를 생성하고 이를 실행해 offset
및 limit
매개변수에 따라 희망사항 목록을 가져옵니다.
UI에 페이징 추가하기
먼저 몇 가지 기본 설정을 정의해 봅시다. app.py
에서 페이지 제한에 대한 변수를 추가합니다.
1 |
# Default setting
|
2 |
pageLimit = 2 |
getWish
파이썬 메서드가 POST 요청을 받게 합니다.
1 |
@app.route('/getWish',methods=['POST']) |
getWish
메서드 내에서 offset
과 limit
을 읽고 이를 sp_GetWishByUserMySQL
저장 프로시저를 호출할 때 전달합니다.
1 |
_limit = pageLimit |
2 |
_offset = request.form['offset'] |
3 |
|
4 |
|
5 |
con = mysql.connect() |
6 |
cursor = con.cursor() |
7 |
cursor.callproc('sp_GetWishByUser',(_user,_limit,_offset)) |
8 |
wishes = cursor.fetchall() |
9 |
|
10 |
userHome.html
의 GetWishes
자바스크립트 함수에서는 POST 요청을 만들고 offset
값을 전달하도록 수정합니다.
1 |
function GetWishes() { |
2 |
$.ajax({ |
3 |
url: '/getWish', |
4 |
type: 'POST', |
5 |
data: { |
6 |
offset: 0 |
7 |
},
|
8 |
success: function(res) { |
9 |
|
10 |
var wishObj = JSON.parse(res); |
11 |
$('#ulist').empty(); |
12 |
$('#listTemplate').tmpl(wishObj).appendTo('#ulist'); |
13 |
|
14 |
},
|
15 |
error: function(error) { |
16 |
console.log(error); |
17 |
}
|
18 |
});
|
19 |
}
|
모든 변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 이메일 주소와 비밀번호로 로그인하면 화면에 두 개의 레코드만 표시될 것입니다.



따라서 데이터베이스 부분은 잘 작동하고 있습니다. 다음으로 사용자 홈 페이지에 페이징 UI를 추가해야 합니다. 그러고 나면 사용자가 데이터를 열람할 수 있을 것입니다.
여기서는 부트스트랩 페이징 컴포넌트를 사용하겠습니다. userHome.html
을 열고 #ulist
UL 다음에 다음과 같은 HTML 코드를 추가합니다.
1 |
<nav>
|
2 |
<ul class="pagination"> |
3 |
<li>
|
4 |
<a href="#" aria-label="Previous"> |
5 |
<span aria-hidden="true">«</span> |
6 |
</a>
|
7 |
</li>
|
8 |
<li><a href="#">1</a> |
9 |
</li>
|
10 |
<li><a href="#">2</a> |
11 |
</li>
|
12 |
<li><a href="#">3</a> |
13 |
</li>
|
14 |
<li><a href="#">4</a> |
15 |
</li>
|
16 |
<li><a href="#">5</a> |
17 |
</li>
|
18 |
<li>
|
19 |
<a href="#" aria-label="Next"> |
20 |
<span aria-hidden="true">»</span> |
21 |
</a>
|
22 |
</li>
|
23 |
</ul>
|
24 |
</nav>
|
변경사항을 저장하고 서버를 다시 시작합니다. 로그인에 성공하면 희망사항 하단에 페이징된 모습을 볼 수 있습니다.



페이징을 동적으로 만들기
위의 페이징은 페이징이 어떻게 표시될지 보여줍니다. 그러나 페이징이 기능하게 만들려면 데이터베이스의 레코드 수를 기반으로 동적으로 페이징을 만들어야 합니다.
페이징을 만들려면 데이터베이스에서 이용 가능한 총 레코드 수가 필요합니다. 따라서 sp_GetWishByUser
MySQL 저장 프로시저에서 사용 가능한 총 레코드 수를 출력 매개변수로 반환하도록 수정해 봅시다.
1 |
USE `BucketList`; |
2 |
DROP procedure IF EXISTS `sp_GetWishByUser`; |
3 |
|
4 |
DELIMITER $$ |
5 |
USE `BucketList`$$ |
6 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetWishByUser`( |
7 |
IN p_user_id bigint, |
8 |
IN p_limit int, |
9 |
IN p_offset int, |
10 |
out p_total bigint |
11 |
)
|
12 |
BEGIN
|
13 |
|
14 |
select count(*) into p_total from tbl_wish where wish_user_id = p_user_id; |
15 |
|
16 |
SET @t1 = CONCAT( 'select * from tbl_wish where wish_user_id = ', p_user_id, ' order by wish_date desc limit ',p_limit,' offset ',p_offset); |
17 |
PREPARE stmt FROM @t1; |
18 |
EXECUTE stmt; |
19 |
DEALLOCATE PREPARE stmt; |
20 |
END$$ |
21 |
|
22 |
DELIMITER ; |
수정된 저장 프로시저에서 볼 수 있듯이 p_total
이라는 새 출력 매개변수를 추가하고 사용자 ID를 기반으로 희망사항의 총 개수를 선택했습니다.
또한 getWish
파이썬 메서드에서 출력 매개변수를 전달하도록 수정합니다.
1 |
_limit = pageLimit |
2 |
_offset = request.form['offset'] |
3 |
_total_records = 0 |
4 |
|
5 |
|
6 |
con = mysql.connect() |
7 |
cursor = con.cursor() |
8 |
cursor.callproc('sp_GetWishByUser',(_user,_limit,_offset,_total_records)) |
9 |
wishes = cursor.fetchall() |
10 |
|
11 |
cursor.close() |
12 |
|
13 |
cursor = con.cursor() |
14 |
cursor.execute('SELECT @_sp_GetWishByUser_3'); |
15 |
|
16 |
outParam = cursor.fetchall() |
위 코드에서 볼 수 있듯이 저장 프로시저를 호출하면 커서를 닫고 새 커서를 열어 반환된 매개변수를 선택합니다.
앞에서는 파이썬 메서드에서 희망사항 목록을 반환했습니다. 이제 반환된 JSON에도 총 레코드 수를 포함해야 합니다. 따라서 희망사항 사전을 또 다른 리스트에다 만들고 메인 리스트에 해당 희망사항과 레코드 수를 추가할 것입니다. 다음은 수정된 getWish
파이썬 메서드입니다.
1 |
response = [] |
2 |
wishes_dict = [] |
3 |
|
4 |
for wish in wishes: |
5 |
wish_dict = { |
6 |
'Id': wish[0], |
7 |
'Title': wish[1], |
8 |
'Description': wish[2], |
9 |
'Date': wish[4]} |
10 |
wishes_dict.append(wish_dict) |
11 |
|
12 |
response.append(wishes_dict) |
13 |
response.append({'total':outParam[0][0]}) |
14 |
|
15 |
return json.dumps(response) |
GetWishes
자바스크립트 함수에서는 성공 콜백 내부에 콘솔 로그를 추가합니다.
1 |
console.log(res); |
모든 변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 이메일 주소와 비밀번호로 로그인한 후 사용자 홈페이지에서 브라우저 콘솔을 확인합니다. 그럼 아래에 표시된 것과 비슷한 응답을 볼 수 있을 것입니다.
1 |
[
|
2 |
[{
|
3 |
"Date": "Sun, 15 Feb 2015 15:10:45 GMT", |
4 |
"Description": "wwe", |
5 |
"Id": 5, |
6 |
"Title": "wwe" |
7 |
}, { |
8 |
"Date": "Sat, 24 Jan 2015 00:13:50 GMT", |
9 |
"Description": "Travel to Spain", |
10 |
"Id": 4, |
11 |
"Title": "Spain" |
12 |
}], { |
13 |
"total": 5 |
14 |
}
|
15 |
]
|
응답에서 받은 총 개수를 사용해 총 페이지 수를 구할 수 있습니다.
1 |
var total = wishObj[1]['total']; |
2 |
var pageCount = total/itemsPerPage; |
총 항목 수를 itemsPerPage
수로 나누면 필요한 페이지 수가 나옵니다. 그런데 이것은 total이 itemsPerPage
의 배수일 때만 유효합니다. 그렇지 않은 경우에는 이를 확인하고 이에 따라 페이지 수를 처리해야 합니다.
1 |
var pageRem = total%itemsPerPage; |
2 |
if(pageRem !=0 ){ |
3 |
pageCount = Math.floor(pageCount)+1; |
4 |
}
|
그러면 올바른 페이지 수를 구할 수 있습니다.
이제 총 페이지 수를 구했으므로 페이징 HTML을 동적으로 생성하겠습니다. 앞에서 추가한 페이징 HTML에서 LI
요소를 제거합니다.
1 |
<nav> |
2 |
<ul class="pagination"> |
3 |
// li we'll create dynamically
|
4 |
</ul> |
5 |
</nav> |
GetWishes
성공 콜백에서는 jQuery를 이용해 이전 링크를 동적으로 만들어 보겠습니다.
1 |
var prevLink = $('<li/>').append($('<a/>').attr({ |
2 |
'href': '#' |
3 |
}, { |
4 |
'aria-label': 'Previous' |
5 |
})
|
6 |
.append($('<span/>').attr('aria-hidden', 'true').html('«'))); |
7 |
|
8 |
$('.pagination').append(prevLink); |
위의 코드에서는 이전 버튼 링크를 만들어 페이징 UL에 추가했습니다.
위의 변경 사항을 저장하고 서버를 다시 시작합니다. 로그인에 성공하면 리스트 하단에 놓인 이전 링크를 볼 수 있을 것입니다.



이와 마찬가지로 페이지 수에 따라 각 페이지에 추가합니다.
1 |
for (var i = 0; i < pageCount; i++) { |
2 |
var page = $('<li/>').append($('<a/>').attr('href', '#').text(i + 1)); |
3 |
$('.pagination').append(page); |
4 |
}
|
페이지 링크가 추가된 후 다음(Next) 링크도 추가합시다.
1 |
var nextLink = $('<li/>').append($('<a/>').attr({ |
2 |
'href': '#' |
3 |
}, { |
4 |
'aria-label': 'Next' |
5 |
})
|
6 |
.append($('<span/>').attr('aria-hidden', 'true').html('»'))); |
7 |
|
8 |
$('.pagination').append(nextLink); |
변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 이메일 주소와 비밀번호로 로그인하고 사용자 홈 페이지로 이동하면 페이징을 볼 수 있을 것입니다.



페이지 번호에 클릭 이벤트 연결하기
이제 페이징을 동작하게 만들 주요 로직이 나옵니다. 각 페이지 인덱스에 GetWishes
자바스크립트 함수를 호출하는 클릭 이벤트 호출을 추가하려고 합니다. 먼저 페이지 번호를 표시하는 앵커 요소에 클릭 이벤트를 추가하겠습니다.
1 |
for (var i = 0; i < pageCount; i++) { |
2 |
|
3 |
var aPage = $('<a/>').attr('href', '#').text(i + 1); |
4 |
|
5 |
$(aPage).click(function() { |
6 |
|
7 |
});
|
8 |
|
9 |
var page = $('<li/>').append(aPage); |
10 |
$('.pagination').append(page); |
11 |
|
12 |
}
|
페이지 앵커에 onclick 이벤트를 추가했습니다. 따라서 클릭할 때마다 GetWishes
함수를 호출하고 offset
을 전달할 것입니다. 따라서 for 루프 외부에 offset
을 선언합니다.
1 |
var offset = 0; |
클릭 이벤트 호출 내에서 GetWishes
함수를 호출합니다.
1 |
GetWishes(offset); |
또한 표시된 레코드 수에 따라 offset
도 증가시킵니다.
1 |
offset = offset + 2; |
그러나 GetWishes
함수가 호출될 때마다 offset
의 값은 항상 마지막으로 설정될 것입니다. 따라서 GetWishes
함수에 올바른 오프셋을 전달하기 위해 자바스크립트 클로저(JavaScript Closure)를 사용하겠습니다.
1 |
var offset = 0; |
2 |
|
3 |
for (var i = 0; i < pageCount; i++) { |
4 |
|
5 |
var aPage = $('<a/>').attr('href', '#').text(i + 1); |
6 |
|
7 |
$(aPage).click(function(offset) { |
8 |
return function() { |
9 |
GetWishes(offset); |
10 |
}
|
11 |
}(offset)); |
12 |
|
13 |
var page = $('<li/>').append(aPage); |
14 |
$('.pagination').append(page); |
15 |
offset = offset + itemsPerPage; |
16 |
|
17 |
}
|
위의 모든 변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 로그인 정보로 로그인하고 사용자 홈 페이지로 이동한 후 페이징 UL의 페이지를 클릭해 보십시오.
다음으로 이전 페이지 링크와 다음 페이지 링크를 구현하겠습니다. 조금 복잡해 보일 수도 있으므로 구현을 시작하기 전에 조금 설명하겠습니다.
여기서는 한 번에 5개의 페이지를 표시하겠습니다. 다음 및 이전 링크를 통해 사용자는 다음 5개 및 이전 5개 페이지로 각각 이동할 수 있습니다. 시작 페이지와 끝 페이지의 값을 저장하고 다음 버튼 클릭과 이전 버튼 클릭에 대해 계속 업데이트합니다. 먼저 userHome.html
페이지에 두 개의 숨김(hidden) 필드를 추가해 보겠습니다.
1 |
<input type="hidden" id="hdnStart" value="1" /> |
2 |
<input type="hidden" id="hdnEnd" value="5"/> |
GetWishes
성공 콜백에서는 .pagination
UL을 비운 후에 다음 코드를 추가해서 최신 시작 페이지와 끝 페이지를 가져옵니다.
1 |
$('.pagination').empty(); |
2 |
|
3 |
var pageStart = $('#hdnStart').val(); |
4 |
var pageEnd = $('#hdnEnd').val(); |
1~5페이지를 표시할 때는 이전 버튼 링크가 표시되지 않습니다. 표시된 페이지가 5보다 크면 이전 버튼 링크가 표시됩니다.
1 |
if (pageStart > 5) { |
2 |
var aPrev = $('<a/>').attr({ |
3 |
'href': '#' |
4 |
}, { |
5 |
'aria-label': 'Previous' |
6 |
})
|
7 |
.append($('<span/>').attr('aria-hidden', 'true').html('«')); |
8 |
|
9 |
$(aPrev).click(function() { |
10 |
// Previous button logic
|
11 |
});
|
12 |
|
13 |
var prevLink = $('<li/>').append(aPrev); |
14 |
$('.pagination').append(prevLink); |
15 |
}
|
사용자가 이전 버튼을 클릭하면 hdnStart
및 hdnEnd
값을 재설정한 후 GetWishes
자바스크립트 함수를 호출합니다.
1 |
$(aPrev).click(function() { |
2 |
$('#hdnStart').val(Number(pageStart) - 5); |
3 |
$('#hdnEnd').val(Number(pageStart) - 5 + 4); |
4 |
GetWishes(Number(pageStart) - 5); |
5 |
});
|
다음으로, 시작 페이지와 끝 페이지를 기반으로 페이지 링크를 반복 생성해서 .pagination
UL을 추가합니다.
1 |
for (var i = Number(pageStart); i <= Number(pageEnd); i++) { |
2 |
|
3 |
if (i > pageCount) { |
4 |
break; |
5 |
}
|
6 |
|
7 |
|
8 |
var aPage = $('<a/>').attr('href', '#').text(i); |
9 |
|
10 |
// Attach the page click event
|
11 |
$(aPage).click(function(i) { |
12 |
return function() { |
13 |
GetWishes(i); |
14 |
}
|
15 |
}(i)); |
16 |
|
17 |
var page = $('<li/>').append(aPage); |
18 |
|
19 |
// Attach the active page class
|
20 |
if ((_page) == i) { |
21 |
$(page).attr('class', 'active'); |
22 |
}
|
23 |
|
24 |
$('.pagination').append(page); |
25 |
|
26 |
|
27 |
}
|
총 페이지 수와 페이지 시작 값을 비교해서 다음 버튼 링크를 표시할지 결정합니다.
1 |
if ((Number(pageStart) + 5) <= pageCount) { |
2 |
var nextLink = $('<li/>').append($('<a/>').attr({ |
3 |
'href': '#' |
4 |
}, { |
5 |
'aria-label': 'Next' |
6 |
})
|
7 |
.append($('<span/>').attr('aria-hidden', 'true').html('»').click(function() { |
8 |
$('#hdnStart').val(Number(pageStart) + 5); |
9 |
$('#hdnEnd').val(Number(pageStart) + 5 + 4); |
10 |
GetWishes(Number(pageStart) + 5); |
11 |
|
12 |
})));
|
13 |
$('.pagination').append(nextLink); |
14 |
}
|
위의 코드에서 볼 수 있듯이 다음 버튼을 클릭할 때 hdnStart
및 hdnEnd
버튼 값을 재설정하고 GetWishes
자바스크립트 함수를 호출합니다.
다음은 최종 GetWishes
자바스크립트 함수입니다.
1 |
function GetWishes(_page) { |
2 |
|
3 |
var _offset = (_page - 1) * 2; |
4 |
|
5 |
$.ajax({ |
6 |
url: '/getWish', |
7 |
type: 'POST', |
8 |
data: { |
9 |
offset: _offset |
10 |
},
|
11 |
success: function(res) { |
12 |
|
13 |
var itemsPerPage = 2; |
14 |
|
15 |
var wishObj = JSON.parse(res); |
16 |
|
17 |
$('#ulist').empty(); |
18 |
$('#listTemplate').tmpl(wishObj[0]).appendTo('#ulist'); |
19 |
|
20 |
var total = wishObj[1]['total']; |
21 |
var pageCount = total / itemsPerPage; |
22 |
var pageRem = total % itemsPerPage; |
23 |
if (pageRem != 0) { |
24 |
pageCount = Math.floor(pageCount) + 1; |
25 |
}
|
26 |
|
27 |
|
28 |
$('.pagination').empty(); |
29 |
|
30 |
var pageStart = $('#hdnStart').val(); |
31 |
var pageEnd = $('#hdnEnd').val(); |
32 |
|
33 |
|
34 |
|
35 |
|
36 |
if (pageStart > 5) { |
37 |
var aPrev = $('<a/>').attr({ |
38 |
'href': '#' |
39 |
}, { |
40 |
'aria-label': 'Previous' |
41 |
})
|
42 |
.append($('<span/>').attr('aria-hidden', 'true').html('«')); |
43 |
|
44 |
$(aPrev).click(function() { |
45 |
$('#hdnStart').val(Number(pageStart) - 5); |
46 |
$('#hdnEnd').val(Number(pageStart) - 5 + 4); |
47 |
GetWishes(Number(pageStart) - 5); |
48 |
});
|
49 |
|
50 |
var prevLink = $('<li/>').append(aPrev); |
51 |
$('.pagination').append(prevLink); |
52 |
}
|
53 |
|
54 |
|
55 |
|
56 |
for (var i = Number(pageStart); i <= Number(pageEnd); i++) { |
57 |
|
58 |
if (i > pageCount) { |
59 |
break; |
60 |
}
|
61 |
|
62 |
|
63 |
var aPage = $('<a/>').attr('href', '#').text(i); |
64 |
|
65 |
$(aPage).click(function(i) { |
66 |
return function() { |
67 |
GetWishes(i); |
68 |
}
|
69 |
}(i)); |
70 |
var page = $('<li/>').append(aPage); |
71 |
|
72 |
if ((_page) == i) { |
73 |
$(page).attr('class', 'active'); |
74 |
}
|
75 |
|
76 |
$('.pagination').append(page); |
77 |
|
78 |
|
79 |
}
|
80 |
if ((Number(pageStart) + 5) <= pageCount) { |
81 |
var nextLink = $('<li/>').append($('<a/>').attr({ |
82 |
'href': '#' |
83 |
}, { |
84 |
'aria-label': 'Next' |
85 |
})
|
86 |
.append($('<span/>').attr('aria-hidden', 'true').html('»').click(function() { |
87 |
$('#hdnStart').val(Number(pageStart) + 5); |
88 |
$('#hdnEnd').val(Number(pageStart) + 5 + 4); |
89 |
GetWishes(Number(pageStart) + 5); |
90 |
|
91 |
})));
|
92 |
$('.pagination').append(nextLink); |
93 |
}
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
},
|
99 |
error: function(error) { |
100 |
console.log(error); |
101 |
}
|
102 |
});
|
103 |
}
|
모든 변경사항을 저장하고 서버를 다시 시작합니다. 유효한 이메일 주소와 비밀번호를 사용해서 로그인합니다. 사용자 희망사항 목록에 대해 페이징이 완전히 기능하는 모습을 볼 수 있을 것입니다.
정리
이번 연재 기사에서는 사용자 홈 페이지의 희망사항 목록에 대한 페이징 기능을 구현했습니다. MySQL 저장 프로시저를 이용해 데이터를 조회하고 해당 데이터와 jQuery, 부트스트랩을 이용해 페이징을 만드는 방법을 살펴봤습니다.
다음 6부에서는 애플리케이션에 파일 업로드 기능을 구현하겠습니다.
이 튜토리얼의 소스코드는 깃허브에서 확인하실 수 있습니다.
아래 댓글로 여러분의 생각을 알려주세요!