解説コードを読んでみよう
このサンプルでわかること
- ボタンを押したときに、ajax でサーバーの PHP へ状態を送る方法
- PHP が返した JSON を受け取って、画面と円グラフを作り直す方法
- 保存した状態を、ページを開いたときに PHP で読み込んで表示する方法
ひとつ前のサンプル「レッスンの進み具合を表示する」では、再読み込みすると完了状態が消えてしまいました。ここでは状態をサーバーに保存して、残るようにします。
いくつか「完了」にしてから、ページを再読み込みしてみてください。円グラフが同じ進み具合から始まります。
使っているファイル
- lesson.phpデモのページ
- save.phpajax で呼ばれ、完了状態を保存して JSON を返す
- lesson_store.php完了状態を読み書きする関数
- js/jquery.circliful.jsCircliful 本体
- css/jquery.circliful.cssCircliful 用の CSS
1. 保存の仕組み(セッション)
見本なので、データベースの代わりにセッションに保存しています。セッションは、サーバーが見ている人ごとに用意する一時的な保存場所です。ほかの人の画面には影響せず、ブラウザを閉じたり時間がたったりすると消えます。
<?php
// レッスンの完了状態を読み書きする関数
// 見本なので、データベースの代わりに「セッション」に保存します
// (セッション=サーバーが、見ている人ごとに用意してくれる一時的な保存場所)
const LESSON_COUNT = 4;
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// 保存されている完了状態を読む(まだ何もなければ、全部「未完了」)
function lessons_load(): array
{
$saved = $_SESSION['lessons'] ?? null;
if (!is_array($saved) || count($saved) !== LESSON_COUNT) {
$saved = array_fill(0, LESSON_COUNT, false);
}
return array_map('boolval', array_values($saved));
}
// $no 番目(0 から数える)のレッスンの完了状態を保存する
function lessons_save(int $no, bool $done): void
{
$lessons = lessons_load();
$lessons[$no] = $done;
$_SESSION['lessons'] = $lessons;
}
// 全部「未完了」に戻す
function lessons_reset(): void
{
$_SESSION['lessons'] = array_fill(0, LESSON_COUNT, false);
}
// 画面に必要な数字をまとめて返す
function lessons_summary(): array
{
$done = lessons_load();
$count = count(array_filter($done));
return [
'done' => $done, // 例: [true, false, true, false]
'count' => $count, // 完了した数
'ratio' => (int)round($count / LESSON_COUNT * 100), // 進み具合(%)
];
}
$_SESSION に入れた値は、同じ人が次にページを開いたときにも読み出せます。使う前に session_start() を呼ぶ決まりです。
2. ページを開いたときに、保存済みの状態を表示する
ページの先頭で lessons_summary() を呼び、完了したボタンには最初から ON クラスと「完了済」の文字を付けておきます。
<?php foreach ($titles as $i => $title): ?>
<?php $on = $state['done'][$i]; ?>
<li>第<?= $i + 1 ?>回 <?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?>
<button type="button" class="btn switch<?= $on ? ' ON' : '' ?>" data-no="<?= $i ?>"><?= $on ? '完了済' : '未完了' ?></button></li>
<?php endforeach; ?>
円グラフも、保存済みの進み具合($state['ratio'])から始めます。
// ① 円グラフを作る(PHP が読み込んだ、保存済みの進み具合から始める)
$("#circle").circliful({
percent: <?= $state['ratio'] ?>
});
3. ボタンを押したら、サーバーに送る
押したボタンの番号(data-no)と、新しい状態(完了なら 1、未完了なら 0)を $.post() で save.php に送ります。
// ② 完了ボタンを押したら、新しい状態を save.php に送る
$("button.switch").on("click", function(){
var done = $(this).hasClass("ON") ? 0 : 1; // 今と反対の状態にする
send({ action: "save", no: $(this).data("no"), done: done });
});
4. save.php で保存して、今の状態を返す
受け取った番号が 0〜3 の整数かを filter_var() で確かめてから保存します。最後に、今の状態をまとめて JSON で返します。
<?php
// ajax で受け取った完了状態を保存して、今の状態を JSON で返す
require_once __DIR__ . '/lesson_store.php';
header('Content-Type: application/json; charset=UTF-8');
$action = $_POST['action'] ?? '';
if ($action === 'save') {
// レッスンの番号が 0〜3 の整数かどうか確かめる
$no = filter_var($_POST['no'] ?? '', FILTER_VALIDATE_INT,
['options' => ['min_range' => 0, 'max_range' => LESSON_COUNT - 1]]);
if ($no === false) {
http_response_code(400);
echo json_encode(['error' => 'レッスンの番号が正しくありません'], JSON_UNESCAPED_UNICODE);
exit;
}
lessons_save($no, ($_POST['done'] ?? '') === '1');
} elseif ($action === 'reset') {
lessons_reset();
}
echo json_encode(lessons_summary(), JSON_UNESCAPED_UNICODE);
返ってくる JSON は、例えば次のような形です。
{"done":[true,false,true,false],"count":2,"ratio":50}
5. 返ってきた状態で、画面を作り直す
ボタンの見た目・円グラフ・数字を、サーバーが返した状態に合わせて作り直します。画面の表示と保存した中身がずれません。
// ④ サーバーに送り、返ってきた状態で画面を作り直す
function send(data) {
$.post("save.php", data, null, "json")
.done(function(res){
$("button.switch").each(function(i){
$(this).toggleClass("ON", res.done[i]).text(res.done[i] ? "完了済" : "未完了");
});
$("#circle .circle").attr("stroke-dasharray", (res.ratio * 3.6) + ", 20000");
$("#circle .number").text(res.ratio);
$("#cmp").text(res.count);
$("#ratio").text(res.ratio);
$("#msg").text("保存しました");
})
.fail(function(){
$("#msg").text("保存できませんでした。時間をおいて試してください。");
});
}
ブラウザから送られてくる値は、書き換えられているかもしれません。番号の範囲などは、必ず PHP 側でも確かめます。
データベースを使うときは
このページは見本なので、データベースを使わずに動かしています。本物のサービスにするときは、データを取り出す関数の中身だけをデータベースの読み書きに取り替えます。画面の作り方(HTML と JavaScript)は同じままで使えます。
例えば、会員ごとの完了状態を残すなら、次のようなテーブルを作り、lessons_save() の中で書き込みます。
CREATE TABLE lesson_progress (
user_id INT NOT NULL, -- 会員の番号
lesson_no INT NOT NULL, -- レッスンの番号(0〜3)
done TINYINT(1) NOT NULL, -- 完了なら 1
PRIMARY KEY (user_id, lesson_no)
);
function lessons_save(int $no, bool $done): void
{
$stmt = db()->prepare(
'INSERT INTO lesson_progress (user_id, lesson_no, done) VALUES (:user, :no, :done)
ON DUPLICATE KEY UPDATE done = VALUES(done)' // あれば書き換え、なければ追加
);
$stmt->execute([':user' => current_user_id(), ':no' => $no, ':done' => (int)$done]);
}