環境構築からWEBアプリ開発・スマホアプリ開発まで。ときには動画制作やゲームも。

supilog
すぴろぐ

【ルービックキューブのタイマーを作る】第7回 保存した記録を表示する回

【ルービックキューブのタイマーを作る】第7回 保存した記録を表示する回

第7回目の今回は、保存したデータを表示してみる回です。とりあえず、今回は、登録した全件を取得して表示するということを目標にやってみようと思う。

実装

※ルーティングやCSSの紹介は省略します。興味がある方は、ページ下部にあるソースコードから直接見てください。

public/js/database.js

全件取得する処理を追加してみました。こちらです。引数にdatasetを入れているのは、今後の機能として、保存する箱を分けたくなった場合に使おうと思っているからです。最初はdataset=1のデータしか登録されない仕様になってますが、そのうちdataset=2やdataset=3のデータが入り混じってきた時に、dataset=Xのデータのみ抽出するということです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
getData(dataset) {
const transaction = this.db.transaction([this.store], "readonly");
const objectStore = transaction.objectStore(this.store);
const index = objectStore.index("by_dataset");
const request = index.getAll(dataset);
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
resolve(request.result ? request.result : null);
};
request.onerror = (event) => {
reject(new Error("Data retrieval failed"));
};
});
},
getData(dataset) { const transaction = this.db.transaction([this.store], "readonly"); const objectStore = transaction.objectStore(this.store); const index = objectStore.index("by_dataset"); const request = index.getAll(dataset); return new Promise((resolve, reject) => { request.onsuccess = (event) => { resolve(request.result ? request.result : null); }; request.onerror = (event) => { reject(new Error("Data retrieval failed")); }; }); },
getData(dataset) {
    const transaction = this.db.transaction([this.store], "readonly");
    const objectStore = transaction.objectStore(this.store);
    const index = objectStore.index("by_dataset");
    const request = index.getAll(dataset);
    return new Promise((resolve, reject) => {
        request.onsuccess = (event) => {
            resolve(request.result ? request.result : null);
        };
        request.onerror = (event) => {
            reject(new Error("Data retrieval failed"));
        };
    });
},

resources/js/list.js

取得後にやっていることは、「配列のソート」「日時表示のフォーマット整形」「HTML要素の追加」です。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
async function dispData() {
let results = await CubeDB.getData(1);
results.sort(function (a, b) {
return b.date - a.date;
});
document.getElementById('records').innerHTML = '';
results.forEach(result => {
const time = (result.time / 1000).toFixed(3);
const dateTime = new Date(result.date);
const dateText = dateTime.getFullYear().toString().padStart(4, '0')
+ '/' + (dateTime.getMonth() + 1).toString().padStart(2, '0')
+ '/' + dateTime.getDate().toString().padStart(2, '0');
const createElement = '<div class="wrapper sm:flex sm:flex-col"><div class="text-xl font-bold list-time" cubeid="' + result.id + '">' + time + '</div><div class="sm:flex sm:flex-row text-cube-gray"><div>[' + dateText + ']</div><div>' + result.scramble + '</div></div></div>';
document.getElementById('records').insertAdjacentHTML('beforeend', createElement);
});
};
window.addEventListener('load', async function () {
await dispData();
});
async function dispData() { let results = await CubeDB.getData(1); results.sort(function (a, b) { return b.date - a.date; }); document.getElementById('records').innerHTML = ''; results.forEach(result => { const time = (result.time / 1000).toFixed(3); const dateTime = new Date(result.date); const dateText = dateTime.getFullYear().toString().padStart(4, '0') + '/' + (dateTime.getMonth() + 1).toString().padStart(2, '0') + '/' + dateTime.getDate().toString().padStart(2, '0'); const createElement = '<div class="wrapper sm:flex sm:flex-col"><div class="text-xl font-bold list-time" cubeid="' + result.id + '">' + time + '</div><div class="sm:flex sm:flex-row text-cube-gray"><div>[' + dateText + ']</div><div>' + result.scramble + '</div></div></div>'; document.getElementById('records').insertAdjacentHTML('beforeend', createElement); }); }; window.addEventListener('load', async function () { await dispData(); });
async function dispData() {
    let results = await CubeDB.getData(1);
    results.sort(function (a, b) {
        return b.date - a.date;
    });
    document.getElementById('records').innerHTML = '';
    results.forEach(result => {
        const time = (result.time / 1000).toFixed(3);
        const dateTime = new Date(result.date);
        const dateText = dateTime.getFullYear().toString().padStart(4, '0')
            + '/' + (dateTime.getMonth() + 1).toString().padStart(2, '0')
            + '/' + dateTime.getDate().toString().padStart(2, '0');
        const createElement = '<div class="wrapper sm:flex sm:flex-col"><div class="text-xl font-bold list-time" cubeid="' + result.id + '">' + time + '</div><div class="sm:flex sm:flex-row text-cube-gray"><div>[' + dateText + ']</div><div>' + result.scramble + '</div></div></div>';
        document.getElementById('records').insertAdjacentHTML('beforeend', createElement);
    });
};

window.addEventListener('load', async function () {
    await dispData();
});

resources/views/list.blade.php

先程のjsによって、HTMLが追加される場所をこちらに作成。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div id="records" class="">
</div>
<div id="records" class=""> </div>
    <div id="records" class="">
    </div>

確認

イメージ通りに表示できました。

ついでに詳細表示を実装

もうちょっと機能が欲しかったので、追加で詳細画面表示を実装して、削除ボタンを作成します。間違って登録しちゃったデータを消せる機能が欲しかったんですw

resources/views/list.blade.php

詳細画面は、モーダルをポップアップさせようかなと思います。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div id="show" class="modal">
<div class="modal-container">
<div class="modal-close">×</div>
<div class="modal-content">
<div>ID : <span class="id"></span></div>
<div><span class="time"></span></div>
<div><span class="scramble"></span></div>
<div><span class="date"></span></div>
<div class="text-right">
<span class="show-delete material-icons cursor-pointer" cubeid="">delete</span>
</div>
</div>
</div>
</div>
<div id="show" class="modal"> <div class="modal-container"> <div class="modal-close">×</div> <div class="modal-content"> <div>ID : <span class="id"></span></div> <div><span class="time"></span></div> <div><span class="scramble"></span></div> <div><span class="date"></span></div> <div class="text-right"> <span class="show-delete material-icons cursor-pointer" cubeid="">delete</span> </div> </div> </div> </div>
<div id="show" class="modal">
    <div class="modal-container">
        <div class="modal-close">×</div>
        <div class="modal-content">
            <div>ID : <span class="id"></span></div>
            <div><span class="time"></span></div>
            <div><span class="scramble"></span></div>
            <div><span class="date"></span></div>
            <div class="text-right">
                <span class="show-delete material-icons cursor-pointer" cubeid="">delete</span>
            </div>
        </div>
    </div>
</div>

public/js/database.js

主キーである「id」(ここではcubeid)を使って、1件のデータを取得する機能を追加。HTMLから取得したidなので、一旦文字列として取得されていたので、数値に変換して使用しています。(これになかなか気付けなくて、エラーと格闘しました・・)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
getSingleData(cubeid) {
const transaction = this.db.transaction([this.store], "readonly");
const objectStore = transaction.objectStore(this.store);
const request = objectStore.get(Number(cubeid));
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
resolve(request.result ? request.result : null);
};
request.onerror = (event) => {
reject(new Error("Data retrieval failed"));
};
});
},
getSingleData(cubeid) { const transaction = this.db.transaction([this.store], "readonly"); const objectStore = transaction.objectStore(this.store); const request = objectStore.get(Number(cubeid)); return new Promise((resolve, reject) => { request.onsuccess = (event) => { resolve(request.result ? request.result : null); }; request.onerror = (event) => { reject(new Error("Data retrieval failed")); }; }); },
getSingleData(cubeid) {
    const transaction = this.db.transaction([this.store], "readonly");
    const objectStore = transaction.objectStore(this.store);
    const request = objectStore.get(Number(cubeid));
    return new Promise((resolve, reject) => {
        request.onsuccess = (event) => {
            resolve(request.result ? request.result : null);
        };
        request.onerror = (event) => {
            reject(new Error("Data retrieval failed"));
        };
    });
},

それから、削除を行う関数も追加しました。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
removeSingleData(cubeid){
const transaction = this.db.transaction([this.store], "readwrite");
const objectStore = transaction.objectStore(this.store);
const request = objectStore.delete(Number(cubeid));
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
resolve(true);
};
request.onerror = (event) => {
reject(false);
};
});
}
removeSingleData(cubeid){ const transaction = this.db.transaction([this.store], "readwrite"); const objectStore = transaction.objectStore(this.store); const request = objectStore.delete(Number(cubeid)); return new Promise((resolve, reject) => { request.onsuccess = (event) => { resolve(true); }; request.onerror = (event) => { reject(false); }; }); }
removeSingleData(cubeid){
    const transaction = this.db.transaction([this.store], "readwrite");
    const objectStore = transaction.objectStore(this.store);
    const request = objectStore.delete(Number(cubeid));
    return new Promise((resolve, reject) => {
        request.onsuccess = (event) => {
            resolve(true);
        };
        request.onerror = (event) => {
            reject(false);
        };
    });
}

resolveとrejectは今回は、そこまで重要じゃなかったのですが、形としてtrue/falseを返すようにしておきました。必要になったら、修正します

resources/js/list.js

前の項目で実装した表示機能処理の後に以下を追加した。JSによりHTMLを出力しているので、確実に要素が存在するタイミングで処理をしたいという意味で、ここに配置。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const cubeTimes = document.querySelectorAll('.list-time');
cubeTimes.forEach(function (cubeTime) {
cubeTime.addEventListener('click', async function () {
const cubeId = this.getAttribute('cubeid');
const cube = await CubeDB.getSingleData(cubeId);
const time = (cube.time / 1000).toFixed(3);
const dateTime = new Date(cube.date);
const dateText = dateTime.getFullYear().toString().padStart(4, '0')
+ '/' + (dateTime.getMonth() + 1).toString().padStart(2, '0')
+ '/' + dateTime.getDate().toString().padStart(2, '0') + ' '
+ dateTime.getHours().toString().padStart(2, '0') + ':'
+ dateTime.getMinutes().toString().padStart(2, '0')
+ ':' + dateTime.getSeconds().toString().padStart(2, '0');
document.querySelector('#show .id').innerHTML = cube.id;
document.querySelector('#show .time').innerHTML = (time);
document.querySelector('#show .scramble').innerHTML = cube.scramble;
document.querySelector('#show .date').innerHTML = dateText;
document.querySelector('#show .show-delete').setAttribute('cubeid', cube.id);
document.getElementById('show').classList.add('is-active');
});
});
const cubeTimes = document.querySelectorAll('.list-time'); cubeTimes.forEach(function (cubeTime) { cubeTime.addEventListener('click', async function () { const cubeId = this.getAttribute('cubeid'); const cube = await CubeDB.getSingleData(cubeId); const time = (cube.time / 1000).toFixed(3); const dateTime = new Date(cube.date); const dateText = dateTime.getFullYear().toString().padStart(4, '0') + '/' + (dateTime.getMonth() + 1).toString().padStart(2, '0') + '/' + dateTime.getDate().toString().padStart(2, '0') + ' ' + dateTime.getHours().toString().padStart(2, '0') + ':' + dateTime.getMinutes().toString().padStart(2, '0') + ':' + dateTime.getSeconds().toString().padStart(2, '0'); document.querySelector('#show .id').innerHTML = cube.id; document.querySelector('#show .time').innerHTML = (time); document.querySelector('#show .scramble').innerHTML = cube.scramble; document.querySelector('#show .date').innerHTML = dateText; document.querySelector('#show .show-delete').setAttribute('cubeid', cube.id); document.getElementById('show').classList.add('is-active'); }); });
const cubeTimes = document.querySelectorAll('.list-time');
cubeTimes.forEach(function (cubeTime) {
    cubeTime.addEventListener('click', async function () {
        const cubeId = this.getAttribute('cubeid');
        const cube = await CubeDB.getSingleData(cubeId);
        const time = (cube.time / 1000).toFixed(3);
        const dateTime = new Date(cube.date);
        const dateText = dateTime.getFullYear().toString().padStart(4, '0')
            + '/' + (dateTime.getMonth() + 1).toString().padStart(2, '0')
            + '/' + dateTime.getDate().toString().padStart(2, '0') + ' '
            + dateTime.getHours().toString().padStart(2, '0') + ':'
            + dateTime.getMinutes().toString().padStart(2, '0')
            + ':' + dateTime.getSeconds().toString().padStart(2, '0');
        document.querySelector('#show .id').innerHTML = cube.id;
        document.querySelector('#show .time').innerHTML = (time);
        document.querySelector('#show .scramble').innerHTML = cube.scramble;
        document.querySelector('#show .date').innerHTML = dateText;
        document.querySelector('#show .show-delete').setAttribute('cubeid', cube.id);
        document.getElementById('show').classList.add('is-active');
    });
});

あとは、各種クリックした時の挙動を実装。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
document.querySelector('#show .modal-close').addEventListener('click', function () {
document.getElementById('show').classList.remove('is-active');
});
window.addEventListener('click', function (e) {
const modal = document.getElementById('show');
if (e.target === modal) {
modal.classList.remove('is-active');
}
});
document.querySelector('#show .show-delete').addEventListener('click', async function () {
const result = await CubeDB.removeSingleData(this.getAttribute('cubeid'));
document.getElementById('show').classList.remove('is-active');
await dispData();
});
document.querySelector('#show .modal-close').addEventListener('click', function () { document.getElementById('show').classList.remove('is-active'); }); window.addEventListener('click', function (e) { const modal = document.getElementById('show'); if (e.target === modal) { modal.classList.remove('is-active'); } }); document.querySelector('#show .show-delete').addEventListener('click', async function () { const result = await CubeDB.removeSingleData(this.getAttribute('cubeid')); document.getElementById('show').classList.remove('is-active'); await dispData(); });
document.querySelector('#show .modal-close').addEventListener('click', function () {
    document.getElementById('show').classList.remove('is-active');
});

window.addEventListener('click', function (e) {
    const modal = document.getElementById('show');
    if (e.target === modal) {
        modal.classList.remove('is-active');
    }
});

document.querySelector('#show .show-delete').addEventListener('click', async function () {
    const result = await CubeDB.removeSingleData(this.getAttribute('cubeid'));
    document.getElementById('show').classList.remove('is-active');
    await dispData();
});

確認

画像からは伝わりませんが、ちゃんと削除するところまで作ることが出来ました。

まとめ

無事に、リスト表示と、1件表示とその削除ができるようになりました!ようやく普通に使っても問題ないレベルになってきた気がします。嬉しいですね。

ここからは、便利にするための機能追加をしていくことになりますが、次回はグラフ化をやってみようと思います。今のところ、「chart.js」を使用しようかなと思っています。ではまた。

今回のソースコード

本日の実装が完了した状態のタグが「v1.0.7」です。

https://github.com/supilog/cube/tree/v1.0.7

  • public/js/database.js
  • resources/js/list.js
  • resources/views/list.blade.php

リンク

「ルービックキューブのタイマーを作る」シリーズ