🃏
個人開発のeigopencilに、フラッシュカード機能を追加しました。
Rails7でHotwireを使ったファイル内でJavaScriptを使用して実装しました。
ブラウザ
<%= render 'users/profile' %>
<div id="container" data-controller="flashcards">
<!--ランダムで5枚のフラッシュカードを表示する-->
<% @microposts.published.sample(5).each do |micropost| %>
<div id="flashcardContainer" class="flashcardContainer m-4 p-3 border-2 border-gray-200 rounded-2xl duration-200">
<!-- 覚えたいフレーズ -->
<div id="content" class="m-2 p-3 border-2 border-gray-200 rounded-2xl duration-200">
<h1>
<%= sanitize micropost.content,
tags: %w(p br h1 h2 h3 h4 h5 h6 ul ol li strong em u s blockquote pre code span div img a),
attributes: %w(class style src alt href data-*),
class: "text-xl text-gray-700 font-medium"
%>
</h1>
<div class="text-right">
<span class="countBadge inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-blue-700/10 ring-inset"> <%= (micropost.count || 0) %>/10</span>
</div>
</div>
<div id="openAnswerAndJudge" class="text-center">
<button class="open bg-gray-100 text-gray-800 rounded hover:bg-gray-200 transition duration-300 px-4 py-2 mr-4">👀</button>
</div>
<div>
<div class="answer m-2 p-3 border-2 border-gray-200 rounded-2xl duration-200">
<%= sanitize micropost.answer,
tags: %w(p br h1 h2 h3 h4 h5 h6 ul ol li strong em u s blockquote pre code span div img a),
attributes: %w(class style src alt href data-*),
class: "text-xl text-gray-700 font-medium"
%>
</div>
<div class="judge text-center">
<button class="incorrect bg-gray-100 text-gray-800 rounded hover:bg-gray-200 transition duration-300 px-4 py-2 mr-4">💀</button>
<button class="correct bg-gray-100 text-gray-800 rounded hover:bg-gray-200 transition duration-300 px-4 py-2 mr-4">✅</button>
</div>
</div>
</div><!--flashcardContainer-->
<% end %>
<div id="congratulationsCard" class="text-center w-full h-screen m-2 p-3 border-2 border-gray-200 rounded-2xl duration-200">
<h1 class="font-bold text-3xl">ナイスです!</h1>
<div class="flex justify-center p-5 m-5">
<%= link_to "もう一度", flashcards_path, class: "bg-gray-50 text-gray-800 rounded hover:bg-gray-200 transition duration-300 m-2 p-2" %>
<%= link_to "ホームに戻る", user_path, class: "bg-gray-50 text-gray-800 rounded hover:bg-gray-200 transition duration-300 m-2 p-2" %>
</div>
</div>
</div>
ブラウザのDOM操作
import { Controller } from "@hotwired/stimulus"
export default class extends Controller{
connect(){
///////////////////////////////////////////////////////////////
//仕様//
//flashcardの問題が最初に表示されており、
//「答えを見るボタン」「答え」「マルボタン」「バツボタン」が非表示(hidden=true)になっている
//0.Rails側で、HTML要素がすでにループされているものがブラウザに届く。
//1.まず0のソースコードをフロント側(DOM)でも全てloopさせて、配列として変数に格納しておく。
//2.今度はそのメモリ(配列)をDOM側でloopをして色々と実行していく。
//////////////////////////////////////////////////////////////
// 1.まず0をフロント側で要素を取得し、配列(メモリ)に記録しておく。
const flashcardContainers = document.querySelectorAll(".flashcardContainer");
const opens = document.querySelectorAll(".open");
const answers = document.querySelectorAll(".answer");
const judges = document.querySelectorAll(".judge");
const corrects = document.querySelectorAll(".correct");
const incorrects = document.querySelectorAll(".incorrect");
const countBadges = document.querySelectorAll(".countBadge");
const congratulation = document.getElementById("congratulationsCard");
//フラッシュカードの回答を全て終えたら、「congratulation要素」を表示させたいので最初はhidden
congratulation.hidden = true;
//userがどのフラッシュカードやボタンをクリックしたのか識別するために{要素: index}でメモリに保管しておくために初期化。
let flashcardContainersHashmap = {};//全体
let answersHashmap = {};//flashcardの答え
let judgesHashmap = {};//マルかバツかをuserが押せるボタンのcontainer要素
//flashcard全体の要素
flashcardContainers.forEach((flashcardContainer, index) =>{
//userが最後まで回答したら、「flashcard要素」を非表示にし「congratulation要素」を表示したいので最初はhiddenをfalseに。
flashcardContainer.hidden = false;
//回答し終わったflashcardはhiddenをtrueにしたいので、「どのカードをuserが操作したのか」を認識するためにindexを付けておく。
flashcardContainersHashmap[index] = flashcardContainer
})
//answers: 「flashcardの答えが書かれている要素」こちらもindexを取得しておく。
answers.forEach((answer, index) =>{
answer.hidden = true;
answersHashmap[index] = answer
})
//judges:「userが合っていたかどうかを自分で判断するマルバツのボタン」判定を隠しておく。ついでに各要素ごとにindexを記録
judges.forEach((judge, index) =>{
judge.hidden = true;
judgesHashmap[index] = judge
})
//openがクリックされたら、その同じ要素内(つまり同じindex内)にあるanswerとjudgeの要素を
//取得したいので、上記で登録しておいた{要素:index}をここで使う。
//opens: 「flashcardの答えが表示されるボタン」
//このボタンが押されたら、先ほどメモリに格納しておいた配列のindexから探し、「flashcardの答えが書かれている要素」のhiddenをfalseにして表示する。
//さらに、「userが合っていたかどうかを自分で判断するマルバツのボタン」もindexから探し、hiddenをfalseにして表示する。
opens.forEach((open, index) => {
open.addEventListener('click', () => {
answersHashmap[index].hidden = false
judgesHashmap[index].hidden = false
});
});
//correct: 「userが合っていたかどうかを自分で判断するマルバツのボタン」のマルボタン
corrects.forEach((correct, index) => {
correct.addEventListener('click', () => {
//追加機能: フラッシュカードの正解数をcount++して、その変数の値をバックエンドへ送信。
console.log("you are correct!!!");
});
});
//incorrect: 「userが合っていたかどうかを自分で判断するマルバツのボタン」のバツボタン
incorrects.forEach((incorrect, index) => {
incorrect.addEventListener('click', () => {
//do nothing (for now)
console.log("you are incorrect!!!");
});
});
let countCard = 0;
//「userが合っていたかどうかを自分で判断するマルバツのボタン」を囲うcontainer要素
//userがマルバツどちらかをクリックしたら、そのflashcardの要素を非表示にする。
//最後のflashcardをuserが回答し終わったら、そのflashcardも同じように非表示にし、congratulation要素を表示する。
//「最後かどうか」は毎回各flashcard全体の要素が非表示になるたびにcountしていき、flashcard.lengthとcountが一致したら「最後」と認識させる。
judges.forEach((judge, index) => {
judge.addEventListener('click', () => {
flashcardContainersHashmap[index].hidden = true;
countCard++;
//フラッシュカード.lengthになった == 全部消えている
if(countCard == flashcardContainers.length)congratulationsCard();
});
});
//flashcardを全て回答し終わったらcongratulation要素を表示させる。
function congratulationsCard(){
console.log("yay!");
congratulation.hidden = false;
}
}
}
もっと、分かりやすく書けたかもしれませんが、自分の頭の中で考えて「この書き方なら実装できそう」と思ったので、その方法で書いていきました。
leetcodeを解いていたときに、hashmapでindexを記録しておくという方法を知っていたのでそれをこのコードに反映させました。
AIに書かせたらもっと良いコードが出来上がるかもしれませんが、そもそもデータ構造とアルゴリズムを大まかに理解できていない僕がそれを使っても学ぶものが何もなかったり、デバックができないと感じたので自分で考えながらコードを書いていきました。今後もある程度自分で実装できるようになるまではAIは使わずに自分でドキュメントを見たり、考えながら実装していく予定です。
Discussion