前回はカレンダーの日付セルにデータのあるなしに関わらず、リンクを設置し、詳細モーダルから指定日のデータを追加できるようにする機能を実装しました。
今回はスマホ時の収支データ一覧の表示方法を、トグルボタンでユーザーが好みのスタイルに切り替えられる機能を実装します。
トグルボタンの選択状態で要素表示を切り替える
・トグルボタンを作成
・日付ごとまとめて表示する要素の実装
・トグルの状態で表示する要素を切り替える実装
以上の3ステップで進めていきます。
トグルボタンを作成
まずはトグルボタンを作成します。
トグルボタンというのはiPhoneの設定などでよく見かける以下の画像のようなボタンを指します。
このトグルボタンの作成については以下の記事を参考にさせていただきました。
以下をスマホ時のデータ出力部分、終始合計や収入合計を出力している<ul>の下に追記します。
</ul>
<!-- トグルボタンここから追加811行目付近 -->
<div class="p-togglebutton-box">
<label for="toggleStyle" class="u-flex-box">
<span>日付ごとまとめて表示 </span>
<div>
<input type="checkbox" id="toggleStyle" onchange="">
<div class="circle"></div>
<div class="button"></div>
</div>
</label>
</div>
<!-- トグルボタン -->
input要素のtype=checkboxを基礎に作るのですが、ボタン部分はCSSでスタイリングするため少し特殊な構造になっています。
続けてCSSを指定します。css/projectに _togglebutton-box.scss ファイルを作成し以下を記述します。
@use "../global" as g;
.p-togglebutton-box {
position: relative;
margin: 2rem 1rem;
label {
justify-content: space-between;
align-items: center;
span {
font-size: 1.4rem;
}
> div {
position: relative;
input[type="checkbox"] {
position: absolute;
width: 0;
height: 0;
&:checked ~ .button {
background-color: #bee3f4;
}
&:checked ~ .circle {
transform: translateX(100%);
background-color: g.$main-blue;
}
}
.circle {
position: absolute;
top: 0.4rem;
left: 0.4rem;
width: 2.4rem;
height: 2.4rem;
border-radius: 1.2rem;
background-color: #fff;
transition: all 0.3s;
}
.button {
width: 5.5rem;
border-radius: 1.6rem;
height: 3.2rem;
background-color: #ddd;
}
}
}
}
そして以前もやりましたが、projectディレクトリ内に「_index.scss」の一番下に以下を追記し、css直下の「style.scss」を保存します。
@use "togglebutton-box";
以上を指定すると以下のようなトグルボタンが完成します。
日付ごとまとめて表示する要素の実装
ここからはトグルボタンがONになったときに表示する要素を作成します。
完成図は以下の通りです。
これから以下のような少し複雑なプログラムを記述していきます。
(2)のwhile内については現在すでに実装しているので、ここで新たに記述することはほぼありません。
まずは(1)を抜いたプログラムを実装します。
(2)のwhileを<div>で囲います。
); ?> <!-- ?>を追加 -->
<div id="allView" class="p-sp-data-box__allview"> <!--追加-->
<?php while ($stmt_dataoutput->fetch()) : ?> <!--冒頭に「<?」を追加-->
<div class="p-sp-data-box item<?php echo h($id); ?>">
:
省略
:
</div>
<?php endwhile; ?>
</div> <!--追加-->
CSSの設定は必要ありません。また<div>で囲っただけなので画面上は特に変わりません。
そして(1)を表示するときはページネーションはいらないので、囲った<div>の中にページネーションを移動させます。
以上で画像の(2)部分は完了です。
続けてif文部分を修正します。
上記の修正段階では$countを定義していないので、まだ正しく動きません。
続いて(1)部分を実装していくのですが、まずはif文に入る前の、データがある日付を抽出してwhile文で配列に格納していくプログラムを実装します。
以下のプログラムを <?php if($count > 0) : ?> の上に追記します。
<?php
//月データ日付まとめで表示
$date_list = array(); //データがある日付を配列で入れる箱を用意
$count_list = array(); //各日付されているデータ数を配列で入れる箱を用意
$week_list = ['日', '月', '火', '水', '木', '金', '土']; //日本語曜日配列の用意
//日付でグループ化したデータを抽出
$sql = 'SELECT COUNT(*), date FROM records WHERE user_id = ? AND date LIKE ? GROUP BY date';
$stmt = $db->prepare($sql);
$stmt->bind_param('is', $user_id, $month_param);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows();
$stmt->bind_result($count_item, $date_item);
while ($stmt->fetch()) :
$date_list[] = $date_item; //日付データを取り出し配列に入れる
$count_list[] = $count_item; //各日付に登録されているデータ数を取り出し配列に入れる
endwhile;
?>
phpMyAdminで実行すると以上のようにデータが抽出されます。
while文で1つ1つ該当の配列に入れています。
上記の場合はwhile処理が終わると、
$date_list = [‘2022-11-10’, ‘2022-11-16’, ‘2022-11-22’] が、$count_list = [‘1′, ’10’, ‘1’] という配列が出来上がります。
これでデータがある日付の抽出ができたので、日付バナーをfor文で生成する処理を実装します。
以下のプログラムを <?php if($count > 0): ?> のすぐ下に追加します。
<div id="groupView" class="p-sp-data-box__groupview">
<?php for ($i = 0; $i < count($date_list); $i++) : //データがある日付の数だけ以下を繰り返す
$search_date = $date_list[$i]; //このあとのSQLのbind_paramセットする変数(データがある日付)
$create_week = date('w', strtotime($search_date)); //日付の曜日を生成(数値で返される)
$day_of_week = $week_list[$create_week]; //上で返された数値曜日を日本語曜日配列の該当番目の文字列を入れる
?>
<div class="p-toggledate-tab js-toggle" id="date<?php echo h($search_date); ?>" onclick="">
<p class="date">
<?php echo date('n月j日', strtotime($date_list[$i])); ?> <!--日付を出力-->
<span class="day-of-week">(<?php echo ($day_of_week); ?>)</span> <!--曜日を出力-->
</p>
<p class="count">( <?php echo h($count_list[$i]); ?>件 )</p> <!--件数を出力-->
</div>
<?php endfor; ?>
</div>
先ほどwhile文で生成した配列を使って、日付けバナーを生成しています。
プログラムだけでは少し分かりづらいので、先ほど例で載せたphpMyAdminのデータの場合でどんなデータが入っていくのかまとめました。
ここでもう一度プログラムを振り返ります。
画像の緑枠と赤枠部分は完了したので、続いて青枠部分を実装します。
実装に入る前に、$sql_dataoutput に格納しているSQL文を少し修正します。
//565行目付近(画面出力用表)
//以下1行追記
$add_where_month = 'WHERE records.date LIKE ? AND records.user_id=? ORDER BY date DESC, input_time DESC ';
$add_limit = 'LIMIT ?, ?';
$sql_dataoutput = 'SELECT records.id, records.date, records.title, records.amount,
:
省略
:
LEFT JOIN qr ON records.qr = qr.id '; //WHERE以降を削除、末尾に半角スペース残す
//$add_where_monthを追加
$stmt_dataoutput = $db->prepare($sql_dataoutput . $add_where_month . $add_limit);
//713行目付近(エクセル出力用テーブル)
$stmt = $db->prepare($sql_dataoutput . $add_where_month); //$add_where_monthを追加
//862行目付近(スマホ時一覧全表示)
$stmt_dataoutput = $db->prepare($sql_dataoutput . $add_where_month . $add_limit); //$add_where_month追加
これから実装する、その日のデータだけを抽出するときにSQLを使いまわしたいのですが、WHERE文が少し異なるので上記のようにWHERE文以降を切り離し、新しい変数に入れました。
SQLを細かくしたので、prepareで$sql_dataoutputを使用している箇所に新しく作成した$add_where_monthを文字列連結させています。
下準備が整ったので、日付ラベルの下に表示するデータ出力処理を実装します。
以下を日付バナー要素とendforの間に追記します。
<div class="p-sp-data-box__frame" id="item<?php echo $search_date; ?>">
<?php
$add_where_date = 'WHERE records.date = ? AND records.user_id = ?';
$stmt_dataoutput = $db->prepare($sql_dataoutput . $add_where_date);
$stmt_dataoutput->bind_param('si', $search_date, $user_id);
sql_check($stmt_dataoutput, $db);
$stmt_dataoutput->bind_result(
$id,
$date,
$title,
$amount,
$spending_category,
$income_category,
$type,
$paymentmethod,
$credit,
$qr,
$memo,
$input_time
);
while ($stmt_dataoutput->fetch()) : ?>
<p><?php echo h($date . $title); ?></p>
<?php endwhile; ?>
</div>
まだ完成ではありませんが、上記を追加すると以下のように、日付ラベルとその下のデータの日付が一致するデータが表示されるのがわかります。
それではwhile文の中を修正します。
while ($stmt_dataoutput->fetch()) :
?>
<div class="p-sp-data-box item<?php echo h($id); ?>">
<div class="u-flex-box p-sp-data-box__overview <?php echo $memo !== '' ? 'hasmemo' : ''; ?>">
<p> <?php echo h($title); ?>
<span>
<?php
if ($type === 0 && $spending_category !== null) {
echo '(' . h($spending_category) . ')';
} else if ($type === 1 && $income_category !== null) {
echo '(' . h($income_category) . ')';
} else {
echo "(カテゴリー不明)";
}
?>
<i class="fa-regular fa-message" onclick="showMemo('<?php echo h($memo); ?>')"></i> </span>
</p>
<p class="<?php echo $type === 0 ? 'text-red' : 'text-blue' ?>">
<?php echo h($type) === "0" ? '-¥' . number_format($amount) : ''; ?>
<?php echo h($type) === "1" ? '+¥' . number_format($amount) : ''; ?>
</p>
</div>
<div class="p-sp-data-box__detail">
<p>
<?php
//支払い方法の出力
if ($type === 0 && $paymentmethod !== null) {
echo '支払い方法:' . h($paymentmethod);
} else if ($type === 1) {
echo "";
} else {
echo "支払い方法:不明";
}
?>
</p>
<?php if ($paymentmethod === "クレジット" || $paymentmethod === "スマホ決済") : ?>
<p>
<?php
//クレジット、スマホ決済の詳細出力
if ($paymentmethod === "クレジット") {
if ($credit !== null) {
echo 'カード種類:' . h($credit);
} else {
echo "カード種類:不明";
}
} else if ($paymentmethod === "スマホ決済") {
if ($qr !== null) {
echo 'スマホ決済種類:' . h($qr);
} else {
echo "スマホ決済種類:不明";
}
}
?>
</p>
<?php endif; ?>
</div>
<div class="u-flex-box p-sp-data-box__button">
<form action="./record-edit.php" method="post">
<input type="hidden" name="record_id" value="<?php echo h($id); ?>">
<input type="submit" class="c-button c-button--bg-green edit" id="" value="編 集">
</form>
<a class="c-button c-button--bg-red delete" id="delete<?php echo h($id); ?>Group" href='./delete.php?id=<?php echo h($id); ?>;' onclick="deleteConfirm('<?php echo h($title); ?>', 'delete<?php echo h($id); ?>Group');">削 除</a>
</div>
</div>
<?php endwhile; ?>
構造は既存のスマホ時のデータ一覧とほぼ同様です。
ただ日付部分は必要ないため削除したり、支払い方法や種類がわかりやすいように少し表示方法を変更しています。
日付ごとまとめて表示できるようになりました。
最後に日付バナータップでその日のデータの表示非表示を切り替えられるようにします。
日付バナーのonclick属性にセットする関数を作成します。
function onClickDataBanner(date) {
$("#date" + date).toggleClass("is-active");
$("#item" + date).slideToggle(230);
}
ここの関数はjQueryの方が簡単に実装できるのでjQueryで作成しました。
この関数を日付バナー部分にセットします。
<div class="p-toggledate-tab js-toggle" id="date<?php echo h($search_date); ?>" onclick="onClickDataBanner('<?php echo $search_date; ?>');">
そしてデフォルトではその日のデータ詳細部分は見えないようにするので、データ詳細を囲んでいる要素にhideクラスを付与します。
<div class="p-sp-data-box__frame hide" id="item<?php echo $search_date; ?>">
以上で日付ごとまとめた表示は完成です。
トグルボタンの状態で表示する要素を切り替え
最後にトグルボタンがONのときは日付ごとまとめた一覧を表示させ、OFFのときは既存の一覧を表示するようにします。
JavaSciptの関数で表示の切り替えを実装します。
まずは関数で使う要素の取得変数をimport.jsに追加します。
//スマホデータ表示切り替えinput
const toggleStyle = document.getElementById("toggleStyle");
//スマホデータ切り替え要素
const groupView = document.getElementById("groupView");
const allView = document.getElementById("allView");
そしてfunctios.jsに関数を記述します。
const onChangeListView = () => {
if (toggleStyle.checked) { //トグルボタンがONのとき(日付ごとまとめた表示)
groupView.classList.remove("hide"); //日付ごとまとめた表示要素からhideクラスを削除で表示する
allView.classList.add("hide"); //既存データ一覧表示要素にhideクラスを追加で非表示にする
} else { //トグルボタンがOFFのとき(既存データ一覧表示)
groupView.classList.add("hide"); //日付ごとまとめた表示要素にhideクラスを追加で非表示にする
allView.classList.remove("hide"); //既存データ一覧表示要素からhideクラスを削除で表示する
}
};
この関数をtype=checkboxのinput要素のonchange属性にセットします。
<input type="checkbox" id="toggleStyle" onchange="onChangeListView();">
そしてデフォルトはOFFの状態で表示するので、日付ごとまとめた表示要素にhideクラスをつけます。
<div id="groupView" class="p-sp-data-box__groupview hide">
以下のようにトグルボタンで表示方法が切り替わります。
既存データ一覧表示も日付ごとまとめた表示のように「支払い方法:クレジット」や「カード種類:〇〇〇〇」の表示方法にする場合のコードです。(whileループの中)
<div class="p-sp-data-box item<?php echo h($id); ?>">
<div class="u-flex-box p-sp-data-box__overview <?php echo $memo !== '' ? 'hasmemo' : ''; ?>">
<p> <?php echo h($title); ?>
<span>
<?php
if ($type === 0 && $spending_category !== null) {
echo '(' . h($spending_category) . ')';
} else if ($type === 1 && $income_category !== null) {
echo '(' . h($income_category) . ')';
} else {
echo "(カテゴリー不明)";
}
?>
<i class="fa-regular fa-message" onclick="showMemo('<?php echo h($memo); ?>');"></i> </span>
</p>
<p class="<?php echo $type === 0 ? 'text-red' : 'text-blue' ?>">
<?php echo h($type) === "0" ? '-¥' . number_format($amount) : ''; ?>
<?php echo h($type) === "1" ? '+¥' . number_format($amount) : ''; ?>
</p>
</div>
<div class="p-sp-data-box__detail">
<p><?php echo date('Y/m/d', strtotime($date)); ?></p>
<p>
<?php
//支払い方法の出力
if ($type === 0 && $paymentmethod !== null) {
echo '支払い方法:' . h($paymentmethod);
} else if ($type === 1) {
echo "";
} else {
echo "支払い方法:不明";
}
?>
</p>
<?php if ($paymentmethod === "クレジット" || $paymentmethod === "スマホ決済") : ?>
<p>
<?php
//クレジット、スマホ決済の詳細出力
if ($paymentmethod === "クレジット") {
if ($credit !== null) {
echo 'カード種類:' . h($credit);
} else {
echo "カード種類:不明";
}
} else if ($paymentmethod === "スマホ決済") {
if ($qr !== null) {
echo 'スマホ決済種類:' . h($qr);
} else {
echo "スマホ決済種類:不明";
}
}
?>
</p>
<?php endif; ?>
</div>
<div class="u-flex-box p-sp-data-box__button">
<form action="./record-edit.php" method="post">
<input type="hidden" name="record_id" value="<?php echo h($id); ?>">
<input type="submit" class="c-button c-button--bg-green edit" id="" value="編 集">
</form>
<a class="c-button c-button--bg-red delete" id="delete<?php echo h($id); ?>sp" href='./delete.php?id=<?php echo h($id); ?>&from=index' onclick="deleteConfirm('<?php echo h($title); ?>', 'delete<?php echo h($id); ?>sp');">削 除</a>
</div>
</div>
このままだと月を変更したり、他ページ遷移などでページの再読み込みが行われると、トグルボタンがOFFに戻ってしまうので、表示方法をcookieに保存し再読み込みが行われても表示方法を保持するようにします。
先ほど作成した関数を修正します。
const onChangeListView = () => {
if ((groupView !== null || allView !== null) && toggleStyle.checked) {
groupView.classList.remove("hide");
allView.classList.add("hide");
document.cookie = "dataView=group";
} else if ((groupView !== null || allView !== null) && !toggleStyle.checked) {
groupView.classList.add("hide");
allView.classList.remove("hide");
document.cookie = "dataView=all";
} else { //データがない月の場合
return; //何もしない
}
};
条件文に「(groupView !== null || allView !== null)」を追加しました。これはデータのない月でトグルボタンを操作されるとエラーが出てしまうのを防ぐ目的です。
if文(日付ごとまとめた表示)では、document.cookie = “dataView=group”; でcookieに日付ごとまとめた表示が選択されていることを保存します。
elseif文(既存データ一覧表示)では document.cookie = “dataView=all”; で既存データ一覧表示が選択されていることを保存しています。
トグルボタンをONにするとcookieに値が保存されるのが確認できます。
OFFにしたときは一度再読み込みをすると、cookieの値が変わるのが確認できます。
最後にページ読み込みが行われたときに、データ一覧表示方法のcookieが保存されていたら、その保存された方法で表示するようにする処理を記述します。
以下をindex.phpの<script>内に記述します。
window.onload = function() {
//左辺条件は先程の関数部分と同様、データを表示する要素があるか(データなしのときはfalseで処理は実行されない)
//document.cookie.indexOf('dataView=group') → cookieに名前がdataViewで値がgroupが保存されているか
//保存されていないと-1が返ってくる
if ((groupView !== null || allView !== null) && document.cookie.indexOf('dataView=group') !== -1) {
toggleStyle.checked = true; //トグルボタンをON状態にする
groupView.classList.remove("hide"); //日付ごとまとめた表示要素からhideクラスを削除で表示する
allView.classList.add("hide"); //既存データ一覧表示要素にhideクラスを追加で非表示にする
}
}
以上を記述すると、トグルボタンがONのときはその情報が再読込しても保持されるようになります。
最後に
今回はトグルボタンでスマホ時のデータ一覧の表示方法を切り替える機能を実装しました。
次回は月ごとにのデータの項目別割合をグラフ化する機能を実装します。
最後までお読みいただきありがとうございました。
コメント