Interactive MCQ Note Type

I have a note type of interactive MCQ. The note type is really good and worth it. But when i sometimes click an option or always click the show answer button the back side appears and this delete or reset my choice forcing to choose again. What should i do?
I want when i click an option the back side appears appears automatically saving my choice as it is.

I think people will need your card template (Front, Back, CSS) to help you from here.

You’re right. I am gonna share them.

This is the Front Card:

<div id="question">{{Question}}</div>

<div id="options-container" class="options">
  <div class="option" data-correct="{{Option1Correct}}">{{Option1}}</div>
  <div class="option" data-correct="{{Option2Correct}}">{{Option2}}</div>
  <div class="option" data-correct="{{Option3Correct}}">{{Option3}}</div>
  <div class="option" data-correct="{{Option4Correct}}">{{Option4}}</div>
</div>

<button id="toggle-explanation">Show Explanation</button>
<div id="explanation" style="display: none;">
  {{Explanation}}
</div>

<script>
function shuffleOptions() {
  const container = document.getElementById('options-container');
  const options = Array.from(container.children);
  for (let i = options.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    container.appendChild(options[j]);
  }
}

function checkAnswer(optionElement) {
  const isCorrect = optionElement.getAttribute('data-correct') === 'true';
  if (isCorrect) {
    optionElement.style.backgroundColor = 'lightgreen';
    disableAllOptions();
  } else {
    optionElement.style.backgroundColor = 'red';
    revealCorrectAnswer();
    disableAllOptions();
  }
  saveState();
}

function revealCorrectAnswer() {
  const options = document.querySelectorAll('.option');
  options.forEach(option => {
    if (option.getAttribute('data-correct') === 'true') {
      option.style.backgroundColor = 'lightgreen';
    }
  });
}

function disableAllOptions() {
  const options = document.querySelectorAll('.option');
  options.forEach(option => option.onclick = null);
}

function saveState() {
  const options = document.querySelectorAll('.option');
  options.forEach((option, index) => {
    localStorage.setItem(`option-${index}`, option.style.backgroundColor);
  });
}

function loadState() {
  const options = document.querySelectorAll('.option');
  options.forEach((option, index) => {
    const color = localStorage.getItem(`option-${index}`);
    if (color) {
      option.style.backgroundColor = color;
      if (color === 'red' || color === 'green' || color === 'lightgreen') {
        option.style.pointerEvents = 'none';
      }
    }
  });
}

function toggleExplanation() {
  const explanation = document.getElementById('explanation');
  const button = document.getElementById('toggle-explanation');
  if (explanation.style.display === 'none') {
    explanation.style.display = 'block';
    button.textContent = 'Hide Explanation';
  } else {
    explanation.style.display = 'none';
    button.textContent = 'Show Explanation';
  }
}

document.addEventListener('DOMContentLoaded', () => {
  shuffleOptions();
  loadState();
  const options = document.querySelectorAll('.option');
  options.forEach(option => {
    option.onclick = () => checkAnswer(option);
  });
  document.getElementById('toggle-explanation').onclick = toggleExplanation;
});
</script>
<script> function loadState() { const options = document.querySelectorAll('.option'); options.forEach((option, index) => { const color = localStorage.getItem(`option-${index}`); if (color) { option.style.backgroundColor = color; } }); } document.addEventListener('DOMContentLoaded', loadState); 
</script>
**This is the Back Card:**
{{FrontSide}}
**This is the Styling:**
.card {
    font-family: arial;
    font-size: 20px;
    text-align: center;
    color: black;
    background-color: white;
}

#question {
  font-size: 24px;
  margin-bottom: 20px;
}

.options {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.option {
  padding: 15px;
  border: 2px solid #ccc;
  border-radius: 10px;
  background-color: #f9f9f9;
  cursor: pointer;
  transition: background-color 0.3s;
}

.option:hover {
  background-color: #e0e0e0;
}

#toggle-explanation {
  margin-top: 20px;
  padding: 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  transition: background-color 0.3s;
}

#toggle-explanation:hover {
  background-color: #0056b3;
}

#explanation {
  margin-top: 20px;
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 10px;
  background-color: #f9f9f9;
}

Could anyone edit it

Front

<div id="question">{{Question}}</div>

<div id="options-container" class="options">
  <div class="option" data-correct="{{Option1Correct}}">{{Option1}}</div>
  <div class="option" data-correct="{{Option2Correct}}">{{Option2}}</div>
  <div class="option" data-correct="{{Option3Correct}}">{{Option3}}</div>
  <div class="option" data-correct="{{Option4Correct}}">{{Option4}}</div>
</div>

<button id="toggle-explanation">Show Explanation</button>
<div id="explanation" style="display: none;">
  {{Explanation}}
</div>

<script>
function shuffleOptions() {
  const container = document.getElementById('options-container');
  const options = Array.from(container.children);
  for (let i = options.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    container.appendChild(options[j]);
  }
}

function checkAnswer(optionElement) {
  const isCorrect = optionElement.getAttribute('data-correct') === 'true';
  if (isCorrect) {
    optionElement.style.backgroundColor = 'lightgreen';
    disableAllOptions();
  } else {
    optionElement.style.backgroundColor = 'red';
    revealCorrectAnswer();
    disableAllOptions();
  }
  saveState();
}

function revealCorrectAnswer() {
  const options = document.querySelectorAll('.option');
  options.forEach(option => {
    if (option.getAttribute('data-correct') === 'true') {
      option.style.backgroundColor = 'lightgreen';
    }
  });
}

function disableAllOptions() {
  const options = document.querySelectorAll('.option');
  options.forEach(option => option.onclick = null);
}

function saveState() {
  const options = document.querySelectorAll('.option');
  options.forEach((option, index) => {
    localStorage.setItem(`option-${index}`, option.style.backgroundColor);
  });
}

function loadState() {
  const options = document.querySelectorAll('.option');
  options.forEach((option, index) => {
    const color = localStorage.getItem(`option-${index}`);
    if (color) {
      option.style.backgroundColor = color;
      if (color === 'red' || color === 'green' || color === 'lightgreen') {
        option.style.pointerEvents = 'none';
      }
    }
  });
}

function toggleExplanation() {
  const explanation = document.getElementById('explanation');
  const button = document.getElementById('toggle-explanation');
  if (explanation.style.display === 'none') {
    explanation.style.display = 'block';
    button.textContent = 'Hide Explanation';
  } else {
    explanation.style.display = 'none';
    button.textContent = 'Show Explanation';
  }
}

document.addEventListener('DOMContentLoaded', () => {
  shuffleOptions();
  loadState();
  const options = document.querySelectorAll('.option');
  options.forEach(option => {
    option.onclick = () => checkAnswer(option);
  });
  document.getElementById('toggle-explanation').onclick = toggleExplanation;
});
</script>
<script> function loadState() { const options = document.querySelectorAll('.option'); options.forEach((option, index) => { const color = localStorage.getItem(`option-${index}`); if (color) { option.style.backgroundColor = color; } }); } document.addEventListener('DOMContentLoaded', loadState); 
</script>

Back:

{{FrontSide}}

Styling

.card {
    font-family: arial;
    font-size: 20px;
    text-align: center;
    color: black;
    background-color: white;
}

#question {
  font-size: 24px;
  margin-bottom: 20px;
}

.options {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.option {
  padding: 15px;
  border: 2px solid #ccc;
  border-radius: 10px;
  background-color: #f9f9f9;
  cursor: pointer;
  transition: background-color 0.3s;
}

.option:hover {
  background-color: #e0e0e0;
}

#toggle-explanation {
  margin-top: 20px;
  padding: 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  transition: background-color 0.3s;
}

#toggle-explanation:hover {
  background-color: #0056b3;
}

#explanation {
  margin-top: 20px;
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 10px;
  background-color: #f9f9f9;
}
2 Likes

Could you please tell what modifications you have done?

There wasn’t any modification, this is just front, back and styling separated to help someone more with more knowledge about this topic

Thank you.

You never mentioned which Anki clients you wanted this to work on, desktop, AnkiMobile and/or AnkiDroid.

I’m familiar with the desktop and AnkiDroid javascript quirks but not AnkiMobile specifics, so apologies if this doesn’t work for that. The code below will work on desktop.

New Front

Changes

  • Removed using an DocumentLoaded eventListener as it wasn’t working for me at least. Not sure if this is a thing in AnkiMobile but in desktop and AnkiDroid I’ve managed without using onloads at all.
  • Added a isBackside check using a hidden div that only exists on the back side.
  • Using isBackside, added resetting localStorage in the front side. localStorage persists after reviews even after closing the deck reviewer on desktop so it needs to be reset on each card.
  • Using isBackside made it so that the options aren’t shuffled again when switching to back
  • Added switching to back side on selecting an option with pycmd('ans'). This works on desktop. Not sure about AnkiMobile. AnkiDroid would need to use the AnkiDroid JS API showAnswer()
<div id="question">{{Question}}</div>

<div id="options-container" class="options">
  <div class="option" data-correct="{{Option1Correct}}">{{Option1}}</div>
  <div class="option" data-correct="{{Option2Correct}}">{{Option2}}</div>
  <div class="option" data-correct="{{Option3Correct}}">{{Option3}}</div>
  <div class="option" data-correct="{{Option4Correct}}">{{Option4}}</div>
</div>

<button id="toggle-explanation">Show Explanation</button>
<div id="explanation" style="display: none">{{Explanation}}</div>

<script>
  function shuffleOptions() {
    const container = document.getElementById("options-container");
    const options = Array.from(container.children);
    for (let i = options.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      container.appendChild(options[j]);
    }
  }

  function checkAnswer(optionElement) {
    const isCorrect = optionElement.getAttribute("data-correct") === "true";
    if (isCorrect) {
      optionElement.style.backgroundColor = "lightgreen";
      disableAllOptions();
    } else {
      optionElement.style.backgroundColor = "red";
      revealCorrectAnswer();
      disableAllOptions();
    }
    saveState();
    pycmd("ans");
  }

  function revealCorrectAnswer() {
    const options = document.querySelectorAll(".option");
    options.forEach((option) => {
      if (option.getAttribute("data-correct") === "true") {
        option.style.backgroundColor = "lightgreen";
      }
    });
  }

  function disableAllOptions() {
    const options = document.querySelectorAll(".option");
    options.forEach((option) => (option.onclick = null));
  }

  function saveState() {
    const options = document.querySelectorAll(".option");
    options.forEach((option, index) => {
      localStorage.setItem(`option-${index}`, option.style.backgroundColor);
    });
  }

  function loadState() {
    const options = document.querySelectorAll(".option");
    options.forEach((option, index) => {
      const color = localStorage.getItem(`option-${index}`);
      if (color) {
        option.style.backgroundColor = color;
        if (color === "red" || color === "green" || color === "lightgreen") {
          option.style.pointerEvents = "none";
        }
      }
    });
  }

  function resetState() {
    localStorage.clear();
  }

  function toggleExplanation() {
    const explanation = document.getElementById("explanation");
    const button = document.getElementById("toggle-explanation");
    if (explanation.style.display === "none") {
      explanation.style.display = "block";
      button.textContent = "Hide Explanation";
    } else {
      explanation.style.display = "none";
      button.textContent = "Show Explanation";
    }
  }

  var isBackside = !!document.getElementById("is-backside");
  if (isBackside) {
    shuffleOptions();
  } else {
    resetState();
  }
  loadState();
  var options = document.querySelectorAll(".option");
  options.forEach((option) => {
    option.onclick = () => checkAnswer(option);
  });
  document.getElementById("toggle-explanation").onclick = toggleExplanation;
</script>

New Back

{{FrontSide}}
<div id='is-backside' style="display: none;"></div>

If want to learn about JS for using Anki how should I go about it? You seem like a knowledgeable person so I ask.

My coding experience is basic HTML I learnt in 8th grade in school. Some understanding of CSS gained mostly from observation. Observations that mostly came from Anki’s note templates. I understand the logic behind if { } else { } and basically just that. Is there a good place to start and should I even bother learning it. I don’t want to learn it if it won’t help much with my templates in Anki.

Edit: You’re typing here for an hour? dang. I just expected a quick short answer.

I don’t want to learn it if it won’t help much with my templates in Anki.

You can do so much with Javascript in templates. Whole addons really.

So then, how best to learn…

Using the method of looking at other people’s code, copy-pasting it and figuring out how it works is a fine way to learn but it does require that you know at least somewhat how to code in general. When you know very little about programming, the difficulty in figuring how some piece of code works is too high. That feels to me very much like the concept of comprehensible input in language learning. If the material is too hard, you learn nothing.

So, I think the main hurdles you have are

  1. first learning enough about coding to be able to learn effectively from looking at other people’s code
  2. needing to do that on your free time

I’d recommend dong a structured course on javascript programming first. Something on CodeAcademy, udemy, etc. is fine. Then, as soon as you start to feel that you understand what you’d need to learn to do the stuff you’ve been wanting to do, you could stop doing the course and switch to making that stuff you want to make by looking up just what you need to know to do it, copying code from ChatGPT etc.

You’re typing here for an hour? dang. I just expected a quick short answer.

Haha, I thought so too but then I started thinking about what do I really know about how to learn stuff and went down a rabbit hole. I’m also on team Javascript-addons-are-the-future so I’m happy, if one more person who’s really into Anki also learns Javascript.

1 Like

Sorry, i didn’t think of it. I am using Ankidroid.

Okay, well very little needs to be added to make it work on both desktop and AnkiDroid:

AnkiDroid-compatible Front

  • On AnkiDroid, the function to call is showAnswer() and on desktop it’s pycmd("ans")
<div id="question">{{Question}}</div>

<div id="options-container" class="options">
  <div class="option" data-correct="{{Option1Correct}}">{{Option1}}</div>
  <div class="option" data-correct="{{Option2Correct}}">{{Option2}}</div>
  <div class="option" data-correct="{{Option3Correct}}">{{Option3}}</div>
  <div class="option" data-correct="{{Option4Correct}}">{{Option4}}</div>
</div>

<button id="toggle-explanation">Show Explanation</button>
<div id="explanation" style="display: none">{{Explanation}}</div>

<script>
  function shuffleOptions() {
    const container = document.getElementById("options-container");
    const options = Array.from(container.children);
    for (let i = options.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      container.appendChild(options[j]);
    }
  }

  function handleShowBackside() {
    try {
      // AnkiDroid
      showAnswer();
    } catch {
      // desktop
      pycmd("ans");
    }
  }

  function checkAnswer(optionElement) {
    const isCorrect = optionElement.getAttribute("data-correct") === "true";
    if (isCorrect) {
      optionElement.style.backgroundColor = "lightgreen";
      disableAllOptions();
    } else {
      optionElement.style.backgroundColor = "red";
      revealCorrectAnswer();
      disableAllOptions();
    }
    saveState();
    handleShowBackside();
  }

  function revealCorrectAnswer() {
    const options = document.querySelectorAll(".option");
    options.forEach((option) => {
      if (option.getAttribute("data-correct") === "true") {
        option.style.backgroundColor = "lightgreen";
      }
    });
  }

  function disableAllOptions() {
    const options = document.querySelectorAll(".option");
    options.forEach((option) => (option.onclick = null));
  }

  function saveState() {
    const options = document.querySelectorAll(".option");
    options.forEach((option, index) => {
      localStorage.setItem(`option-${index}`, option.style.backgroundColor);
    });
  }

  function loadState() {
    const options = document.querySelectorAll(".option");
    options.forEach((option, index) => {
      const color = localStorage.getItem(`option-${index}`);
      if (color) {
        option.style.backgroundColor = color;
        if (color === "red" || color === "green" || color === "lightgreen") {
          option.style.pointerEvents = "none";
        }
      }
    });
  }

  function resetState() {
    localStorage.clear();
  }

  function toggleExplanation() {
    const explanation = document.getElementById("explanation");
    const button = document.getElementById("toggle-explanation");
    if (explanation.style.display === "none") {
      explanation.style.display = "block";
      button.textContent = "Hide Explanation";
    } else {
      explanation.style.display = "none";
      button.textContent = "Show Explanation";
    }
  }

  var isBackside = !!document.getElementById("is-backside");
  if (isBackside) {
    shuffleOptions();
  } else {
    resetState();
  }
  loadState();
  var options = document.querySelectorAll(".option");
  options.forEach((option) => {
    option.onclick = () => checkAnswer(option);
  });
  document.getElementById("toggle-explanation").onclick = toggleExplanation;
</script>

Should i add the back card coding of your previous message?

Yeah, that’s unchanged for the AnkiDroid version

I am sorry to bother, but the options don’t shuffle anymore. Can you please fix something like that?

Yeap, passing values from front to back is complicated in AnkiDroid. The selected option and the shuffle state need to be stored in localStorage and then read correctly out, quite a headache.

Front

  • Added another custom attribute option-index to each option so you can get the original position from that during shuffling and use it as the key for the shuffled index.
  • The code could probably be simplified to make it clearer how the storing and reading works.
<div id="question">{{Question}}</div>

<div id="options-container" class="options">
  <div class="option" option-index="0" data-correct="{{Option1Correct}}">
    {{Option1}}
  </div>
  <div class="option" option-index="1" data-correct="{{Option2Correct}}">
    {{Option2}}
  </div>
  <div class="option" option-index="2" data-correct="{{Option3Correct}}">
    {{Option3}}
  </div>
  <div class="option" option-index="3" data-correct="{{Option4Correct}}">
    {{Option4}}
  </div>
</div>

<button id="toggle-explanation">Show Explanation</button>
<div id="explanation" style="display: none">{{Explanation}}</div>

<script>
  var isBackside = !!document.getElementById("is-backside");

  function shuffleOptions() {
    const container = document.getElementById("options-container");
    const options = Array.from(container.children);
    // Shuffle the options
    options.sort(() => Math.random() - 0.5);
    options.forEach((option) => {
      container.appendChild(option);
    });
  }

  function handleShowBackside() {
    try {
      // AnkiDroid
      showAnswer();
    } catch {
      // desktop
      pycmd("ans");
    }
  }

  function checkAnswer(optionElement) {
    const isCorrect = optionElement.getAttribute("data-correct") === "true";
    const selectedOriginalIndex = optionElement.getAttribute("option-index");
    if (isCorrect) {
      optionElement.style.backgroundColor = "lightgreen";
    } else {
      optionElement.style.backgroundColor = "red";
      revealCorrectAnswer();
    }
    disableAllOptions();

    localStorage.setItem(
      `option-${selectedOriginalIndex}`,
      optionElement.style.backgroundColor
    );
    saveState();
    handleShowBackside();
  }

  function revealCorrectAnswer() {
    const options = document.querySelectorAll(".option");
    options.forEach((option) => {
      if (option.getAttribute("data-correct") === "true") {
        option.style.backgroundColor = "lightgreen";
      }
    });
  }

  function disableAllOptions() {
    const options = document.querySelectorAll(".option");
    options.forEach((option) => (option.onclick = null));
  }

  function saveState() {
    const options = document.querySelectorAll(".option");
    options.forEach((option, index) => {
      const originalIndex = option.getAttribute("option-index");
      localStorage.setItem(
        `option-${originalIndex}`,
        option.style.backgroundColor
      );
      localStorage.setItem(`shuffle-${originalIndex}`, index);
    });
  }

  function loadState() {
    const options = document.querySelectorAll(".option");
    if (isBackside) {
      const container = document.getElementById("options-container");
      for (let i = 0; i < options.length; i++) {
        const j = localStorage.getItem(`shuffle-${i}`);
        container.appendChild(options[j]);
      }
    }
    options.forEach((option, index) => {
      const color = localStorage.getItem(`option-${index}`);
      if (color) {
        option.style.backgroundColor = color;
        if (color === "red" || color === "green" || color === "lightgreen") {
          option.style.pointerEvents = "none";
        }
      }
    });
  }

  function resetState() {
    localStorage.clear();
  }

  function toggleExplanation() {
    const explanation = document.getElementById("explanation");
    const button = document.getElementById("toggle-explanation");
    if (explanation.style.display === "none") {
      explanation.style.display = "block";
      button.textContent = "Hide Explanation";
    } else {
      explanation.style.display = "none";
      button.textContent = "Show Explanation";
    }
  }
  if (!isBackside) {
    var options = document.querySelectorAll(".option");
    options.forEach((option) => {
      option.onclick = () => checkAnswer(option);
    });
    shuffleOptions();
    resetState();
  } else {
    disableAllOptions();
    loadState();
  }
  document.getElementById("toggle-explanation").onclick = toggleExplanation;
</script>

2 Likes

Thank you so much. I appreciate it.