How to have instant type:answer feedback?

Hi all,

I would like to have feedback on my type answer cards. (basic note type)

So as I type the answer on the front card, I know if I have correctly typed the answer before flipping the card.

Is this possible?

maybe there is a way to use a script to achieve this? Maybe a script could activate once the correct word is typed in the type answer box which matches a certain field?

for example: lets say I have a field called ‘word’. When I type the contents of the field ‘word’ into the {{type:word}} answer box I would get a sign on the front of the card to signify that I have typed the correct answer before flipping the card.

@kleinerpirat

What about Fill the blanks - Multiple type:cloze support - AnkiWeb?

Yes that would be perfect but it doesn’t work with basic card type only cloze.

I need something for basic card type (non-cloze)

You can pretty much mimic the behavior of basic type using cloze type. So, unless you have to stick with basic type for technical reasons, the add-on should do the work.

anki_MOQWuvRRGz

Its an amazing add on but not suitable for my cards

Below is the javascript file to the fill-the-blanks add on that @01101 mentioned.

@kleinerpirat With the javascript below is it possible to achieve what I’m looking for?


let ifEnabled = true;
let shouldIgnoreCase = false;
let shouldIgnoreAccents = false;

function checkFieldValue(reference, field) {
    if (window.event.keyCode === 13) {
        pycmd("ans");
        return;
    }

    if (! ifEnabled) {
        return;
    }

    let current = field.val().trim();
    // console.log('Cur: ' + current + '; starts? ' + reference.startsWith(current));
    let previous = field.data('lastValue');

    if (current == previous) {
        return;
    }

    cleanUpView(field);

    if (current == '' ) {
        field.data('lastValue', '');
        return;
    }

    if (shouldIgnoreCase) {
        current = current.toLowerCase();
        reference = reference.toLowerCase();
    }

    if (shouldIgnoreAccents) {
        current = current.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
        reference = reference.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
    }

    if (current == reference) {
        field.addClass('st-ok');
    } else {
        if (reference.startsWith(current)) {            
            field.addClass('st-incomplete');
        } else {
            field.addClass('st-error');
        }
    }
    field.data('lastValue', current);
}

function cleanUpView(field) {
    field.removeClass('st-ok');
    field.removeClass('st-incomplete');
    field.removeClass('st-error');
}

function disableInstantFb() {
    ifEnabled = false;
}

function ignoreCaseOnFeedback() {
    shouldIgnoreCase = true;
}

function ignoreAccentsOnFeedback() {
    shouldIgnoreAccents = true;
}

function focusOnFirst() {
    setTimeout(() => {
        try {
            $('#typeans0').focus();
        } catch (error) {
            console.warn(error);
        }        
    }, 300);   
}

Please do not unnecessarily ping people. I know @kleinerpirat knows how to code in JS, but there are plenty of other people who could answer your question too.


I did something like that last year, I was looking for it in my backed up collections but apparently I didn’t bother saving it and simply dumped it. However, retrospectively, I don’t think what it was very useful. Using the add-on linked by @01101 would have been much better, as it would have been less painful to maintain while still providing the same (if not more) feature.

Is there any particular reason why this add-on doesn’t fit you? Because if someone were to do what you need, it would basically end up being what that add-on provides.

Thanks @BlackBeans for caring about my digital wellbeing, but I don’t really mind the pinging :wink: It’s an honor some people reach out to me for help - altough I don’t always find the time for it.


@huyvanphan, try this script for your front template of “Basic (type in the answer)”:

<script>
  (() => {
  /**
   * Type-in-the-answer live feedback for Anki
   * @author Matthias Metelka | @kleinerpirat
   */
    /* Helper functions to keep caret position while typing */
    const getCaretPosition = (contenteditable) => {
      contenteditable.focus();
      let _range = document.getSelection().getRangeAt(0);
      let range = _range.cloneRange();
      range.selectNodeContents(contenteditable);
      range.setEnd(_range.endContainer, _range.endOffset);
      return range.toString().length;
    };
    const setCaretPosition = (contenteditable, pos) => {
      contenteditable.focus();
      document.getSelection().collapse(contenteditable, pos);
    };

    /* Replace input with contenteditable div */
    const input = document.getElementById("typeans");
    const editable = document.createElement("div");
    editable.contentEditable = true;
    editable.id = "typeans";
    editable.innerHTML = input.value;
    input.replaceWith(editable);

    editable.addEventListener("input", () => {
      const pos = getCaretPosition(editable);
      /* Anki uses value attribute to compare answer */
      editable.value = editable.innerText;

      editable.innerHTML = (() => {
        const letters = editable.innerText.split("");
        const word = "{{Answer}}";
        let html = "";
        letters.forEach((letter, i) => {
          html += `<span class="${
            letter == word[i] ? "typeGood" : "typeBad"
          }">${letter}</span>`;
        });
        return html;
      })();
      setCaretPosition(editable, pos);
    });
  })();
</script>

CSS

[contenteditable]#typeans {
  border: thin solid var(--border);
  background: var(--frame-bg);
}

Explanation

Anki uses an input element for its answer box, which cannot contain HTML, only plain text - so it can’t be styled. My script replaces that input element with a contenteditable (the same kind of element that’s used for the fields in Anki’s note editor).

I wrap each letter in its own <span>, using Anki’s classes “typeGood” and “typeBad” for color-coding.

There’s some overhead because updating the inner HTML of such an element moves the caret position to the start.

1 Like

If you don’t want to see the exact location of the error, this becomes a much simpler matter:

<script>
  (() => {
   /**
   * Type-in-the-answer live feedback for Anki (vague variant)
   * @author Matthias Metelka | @kleinerpirat
   */
    const input = document.getElementById("typeans");
    const answer = "{{Answer}}";
    input.addEventListener("input", () => {
      input.classList.add("typed");
      input.classList.toggle(
        "correct",
        input.value == answer.substring(0, input.value.length)
      );
    });
  })();
</script>

CSS

#typeans.typed {
  background: red;
}
#typeans.typed.correct {
  background: green;
}
4 Likes

Thank you so much. The second option is just what I was looking for and works perfectly when studying.
(first option wasn’t suitable for me as it didn’t work with the Vietnamese telex keyboard input system on Microsoft windows).

One small issue is that with the second script, in preview mode, the front of the card just says
“Invalid HTML on card: TypeError: Cannot read property ‘addEventListener’ of null”
Is there a way to fix this to be able to preview my cards?

Wrap input.addEventListener(...) with an if (input) { ... }:

<script>
  (() => {
   /**
   * Type-in-the-answer live feedback for Anki (vague variant)
   * @author Matthias Metelka | @kleinerpirat
   */
    const input = document.getElementById("typeans");
    const answer = "{{Answer}}";
    if (input) {
      input.addEventListener("input", () => {
        input.classList.add("typed");
        input.classList.toggle(
          "correct",
          input.value == answer.substring(0, input.value.length)
        );
      });
    }
  })();
</script>
4 Likes

Thanks a lot! Much appreciated!

Hi Matthias, @kleinerpirat

Your type-in-answer live feedback for anki (vague variant) is working great! I’m really enjoying using it and it has helped my retention rate go up by around 10% so thank you again!

I was wondering if there is a simple way that you could modify your vague variant script so instead of the type-in box turning green for all correct inputs, instead the type-in box will turn yellow if you write correct inputs up until the last letter which forms the whole correct answer and at that point the type-in box would turn green to signify that you have the exact correct answer and that you’re not missing any further inputs/letters.

For example, if the {{Answer}} is Elephant. The type-in box would continue to turn red for any incorrect input but turn yellow as you type the following letters ‘E-l-e-p-h-a-n’ then turn green when you have written the whole correct answer with the the final letter ‘t’.

Hey, some of use want a shot at coding stuff for free as well.

Just kidding, but to answer your request, and since Matthias has already done the heavy lifting, this is an easy fix. You don’t even need to compare substrings anymore.

JS:

<script>
  (() => {
   /**
   * Type-in-the-answer live feedback for Anki (vague variant)
   * @author Matthias Metelka | @kleinerpirat
   */
    const input = document.getElementById("typeans");
    const answer = "{{Answer}}";
    if (input) {
      input.addEventListener("input", () => {
        input.classList.add("typed");
        input.classList.toggle(
          "goodsofar",
          input.value == answer.substring(0, input.value.length)
        );
        input.classList.toggle(
          "correct",
          input.value == answer
        );
      });
    }
  })();
</script>

CSS

#typeans.typed {
  background: red;
}
#typeans.typed.goodsofar {
  background: yellow;
}
#typeans.typed.correct {
  background: green;
}

I haven’t done a lot of testing, but this should do the trick.

5 Likes

Thanks ODeer. Does the trick perfectly! :smiley: