以分頁來說可以分成
- page-based pagination (絕對分頁)
- cursor-based (相對分頁)
所以這邊要做的就是用一個 before
來代表現在是第幾筆資料,載入更多就是依照這個 before
來決定要再載入幾筆資料
1. 更改 sql 子句
- 因此先來改
api_comments.php
這個檔案
在 sql 子句加上 empty($_GET['before']) ? "" : "AND id < ?"
,就是根據 $_GET['before']
是不是空的來決定他要帶什麼參數,如果有傳 $_GET['before']
,結果就是 AND id < $_GET['before']
, 拿到 $_GET['before']
以前資料,也就是載入更多舊資料。
完整sql 子句
$sql = "SELECT nickname, content, created_at FROM discussions WHERE site_key = ? " . (empty($_GET['before']) ? "" : "AND id < ? ") . "ORDER BY id DESC LIMIT 5";
if (empty($_GET['before'])) {
$stmt->bind_param('s', $site_key);
} else {
$stmt->bind_param('si', $site_key, $_GET['before']);
}
補充, sql 句有錯可以用
echo $sql;
把他印出來 debug
2. 要把 id 傳下去
所以從 MySQL 取得資料 fetch_assoc()
那邊多增加 "id" => $row["id"]
while ($row = $result->fetch_assoc()) {
array_push($discussions, array(
"id" => $row["id"],
"nickname" => $row["nickname"],
"content" => $row["content"],
"created_at" => $row["created_at"],
));
}
原本的 sql 子句 SELECT
後面要多加 id
,但這樣等於拿全部欄位,所以直接 SELECT *
就好
$sql = "SELECT * FROM discussions WHERE site_key = ? " . (empty($_GET['before']) ? "" : "AND id < ? ") . "ORDER BY id DESC LIMIT 5";
用 postman 做測試,當傳 before = 30,就會回傳 id = 29、28、27、26、25 的資料,這樣就可以指定 id ,拿它前五筆資料
後端結束
前端串資料
1. 前端把載入資料先包成一個 getComments function
傳入 siteKey, before, callback function
function getCommentsAPI(siteKey, before, cb) {
let url = `http://localhost/hw1_12/api_comments.php?site_key=${siteKey}`
if (before) {
url += '&before=' + before
}
$.ajax({
url,
}).done(function (data) {
cb(data)
});
}
原本拿資料(載入資料)變成
getCommentsAPI(siteKey, null, data => {
if (!data.ok) {
alert(data.message)
return
}
const comments = data.discussions;
for (let comment of comments) {
appendCommentToDOM(commentsDOM, comment)
}
})
2. 加入 載入更多
button
const loadMoreButtonHTML = '<button class="load-more btn btn-primary">載入更多</button>'
這樣載入完把 btn 加進去 commentsDOM.append(loadMoreButtonHTML)
const commentsDOM = $('.comments')
getCommentsAPI(siteKey, null, data => {
if (!data.ok) {
alert(data.message)
return
}
const comments = data.discussions;
for (let comment of comments) {
appendCommentToDOM(commentsDOM, comment)
}
commentsDOM.append(loadMoreButtonHTML)
})
3. 按了載入更多 button,會重複做拿資料這件事,所以再把 getCommentsAPI 這個 function 再包一層 function
這樣要用到拿資料就直接呼叫 getComments()
就好
然後記得每呼叫一次 getComments()
要先把舊的載入更多按鈕給隱藏掉 $('.load-more').hide();
然後會再產生新的載入更多按鈕 commentsDOM.append(loadMoreButtonHTML)
function getComments() {
$('.load-more').hide(); // 把舊的載入更多按鈕給隱藏掉
getCommentsAPI(siteKey, lastId, data => {
const commentsDOM = $('.comments')
if (!data.ok) {
alert(data.message)
return
}
const comments = data.discussions;
for (let comment of comments) {
appendCommentToDOM(commentsDOM, comment)
}
let length = comments.length
lastId = comments[length - 1].id
commentsDOM.append(loadMoreButtonHTML) // 產生新的載入更多按鈕
})
}
然後拿完資料 id 要更新
const comments = data.discussions;
for (let comment of comments) {
appendCommentToDOM(commentsDOM, comment)
}
let length = comments.length
lastId = comments[length - 1].id //最後一個 id
4. 當全部資料秀出來,就不要再有載入更多按鈕
在 getComments 這個 function 裡面來判斷 comments.length 是否小於每次秀五筆資料,如果小於五筆,代表是最後資料了,那就把 載入更多按鈕
隱藏並且 return ,但如果最後一次是剛好五筆,還沒想到解決辦法
let length = comments.length
console.log(length)
if (length < 5) {
$('.load-more').hide();
return
} else {
lastId = comments[length - 1].id
commentsDOM.append(loadMoreButtonHTML)
}
5. 原本在 part2 那邊新增留言直接把資料放上去,但因為要拿到 id 和時間,所以改成再 query API 拿資料
因此在 api_add_comments.php 新增留言這個 API 多增加 sql 子句,讓前端可以拿到資料
$sql = "SELECT * FROM discussions WHERE id = @@IDENTITY";
api_add_comments.php 全部程式
<?php
require_once "conn.php";
header('Content-type:application/json;charset=utf-8'); //要輸出 json 記得要加這行 header,瀏覽器才會知道他是 json 格式的資料
header('Access-Control-Allow-Origin: *');
if (empty($_POST['content']) ||
empty($_POST['nickname']) ||
empty($_POST['site_key'])) {
$json = array(
"ok" => false,
"message" => "Please input missing fields",
);
$response = json_encode($json); //變成 json 的格式
echo $response;
die();
}
$site_key = $_POST['site_key'];
$nickname = $_POST['nickname'];
$content = $_POST['content'];
$sql = "INSERT INTO discussions(site_Key, nickname, content) VALUES (?, ?, ?) ";
$stmt = $conn->prepare($sql);
$stmt->bind_param('sss', $site_key, $nickname, $content);
$result = $stmt->execute();
if (!$result) {
$json = array(
"ok" => false,
"message" => $conn->error,
);
$response = json_encode($json);
echo $response;
die();
}
$sql = "SELECT * FROM discussions WHERE id = @@IDENTITY";
$stmt = $conn->prepare($sql);
$result = $stmt->execute();
$result = $stmt->get_result();
$discussions = array();
$row = $result->fetch_assoc();
array_push($discussions, array(
"id" => $row["id"],
"nickname" => $row["nickname"],
"content" => $row["content"],
"created_at" => $row["created_at"],
));
if (!$result) {
$json = array(
"ok" => false,
"message" => $conn->error,
);
$response = json_encode($json);
echo $response;
die();
}
$json = array(
"ok" => true,
"discussions" => $discussions
);
$response = json_encode($json);
echo $response;
js 的全部程式
function escape(toOutput) {
return toOutput.replace(/\&/g, '&')
.replace(/\</g, '<')
.replace(/\>/g, '>')
.replace(/\"/g, '"')
.replace(/\'/g, ''')
.replace(/\//g, '/');
}
function appendCommentToDOM(container, comment, isPrepend) {
const html = `
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="d-flex">
<div class="card_avartar text-center text-white">
${comment.id}
</div>
<h5 class="card-title">${comment.nickname}</h5>
</div>
<p>${comment.created_at}</p>
</div>
<p class="card-text">${comment.content}</p>
</div>
</div>
`
if (isPrepend) {
container.prepend(html)
} else {
container.append(html)
}
}
function getCommentsAPI(siteKey, before, cb) {
let url = `http://tzutzu858.tw/json/api_comments.php?site_key=${siteKey}`
if (before) {
url += '&before=' + before
}
$.ajax({
url,
}).done(function (data) {
cb(data)
});
}
function getComments() {
$('.load-more').hide();
getCommentsAPI(siteKey, lastId, data => {
const commentsDOM = $('.comments')
if (!data.ok) {
alert(data.message)
return
}
const comments = data.discussions;
for (let comment of comments) {
appendCommentToDOM(commentsDOM, comment)
}
let length = comments.length
if (length < 5) {
$('.load-more').hide();
return
} else {
lastId = comments[length - 1].id
commentsDOM.append(loadMoreButtonHTML)
}
})
}
const siteKey = 'tzu'
const loadMoreButtonHTML = '<button class="load-more btn btn-primary">載入更多</button>'
let lastId = null
$(document).ready(() => {
const commentsDOM = $('.comments')
//第一次載入資料
getComments();
// 載入更多
$('.comments').on('click', '.load-more', () => {
getComments();
})
// 新增留言
$('.add-comment-form').submit(e => {
e.preventDefault();
const newCommentData = {
site_key: 'tzu',
nickname: $('input[name=nickname]').val(),
content: $('textarea[name=content]').val()
}
$.ajax({
type: 'POST',
url: 'http://tzutzu858.tw/json/api_add_comments.php',
data: newCommentData
}).done(function (data) {
if (!data.ok) {
alert(data.message)
return
}
$('input[name=nickname]').val('') // 成功之後傳個空字串清空
$('textarea[name=content]').val('')
const comments = data.discussions;
for (let comment of comments) {
appendCommentToDOM(commentsDOM, comment, true)
}
});
})
})