The front of this card is blank

I have updated the version of ANKI and, when I go to study my multiple choice notes (1 valid answer among 4 alternatives), the message “The front of this card is blank” appears.
What do I have to do to get it working again? I have several hundred cards and I wouldn’t want to lose them.
Thank you.

I can’t answer you in Spanish, unfortunately. It sounds like you need to add a Field Replacement back to your Card template. Manual de Anki

Thanks for your answer, but:

  • The online Anki Manual doesn’t fit to the last version ⁨23.12.1
  • I don’t know programming in HTML :frowning:

When you see the message that the front part of the card is blank, press the “e” key (for Edit), and then in the window that pops up, click on the Cards… button.

Another window will pop up and will show you the “Front template”. What text is visible there?

Thanks! This is the code:

{{#Title}}

{{Title}}

{{/Title}}
{{#Question}}

{{Question}}

{{/Question}}

{{Answers}}
{{QType (0=kprim,1=mc,2=sc)}}

When you hit the “e” key, the first window that popped up was the “Edit Current” window. It should contain fields including Title, Question, Answers. Are all of those fields blank?

If this is really not a valid note, and it has no good information in any of its fields, you can press the “m” key when you see “The front of this card is blank” and then select “Delete Note” from the popup menu that appears.

You can also select Tools / Empty Cards… from the menu, and that will show you if you have any other cards that have the front side blank. If so, there will be a list with clickable links, and you can click on them one by one to investigate. Clicking on each link in that list will bring up the related note in the Browse window.

Thanks, a lot, again for your help. :slightly_smiling_face:

It is an issue with all the cards, for AllInOne notes, in all the decks, really. These notes have all fields correctly filled, there’s no problem at all. I mean, when I browse by notes and open anyone, all the fields are right, but when I browse by cards, all of them say “The front of this card is blank”.

I think this screenshot will show the problem more clearly:

In your previous message, the fields used in the template were called Title, Question, Answers, but in this screenshot you have fields named Pregunta, Tip de pregunta, Respuesta 1 and so on. So something is inconsistent here.

The code in my previous message appears truncated. It was audited by the webmaster previously to its publishing. If you agree, I could send you the code by email.

You can put two lines with
```
one before (above)
and one after (below)
the code from the Front template that you copy-paste here.

Then it should remain intact and display correctly here.

Maybe you can edit your message above to add the ``` lines.

Sorry, I cannot add ``` in my previous message. So:

<script>
    // Loading Persistence
    // https://github.com/SimonLammer/anki-persistence
    // v0.5.2 - https://github.com/SimonLammer/anki-persistence/blob/62463a7f63e79ce12f7a622a8ca0beb4c1c5d556/script.js
    if (void 0 === window.Persistence) { var _persistenceKey = "github.com/SimonLammer/anki-persistence/", _defaultKey = "_default"; if (window.Persistence_sessionStorage = function () { var e = !1; try { "object" == typeof window.sessionStorage && (e = !0, this.clear = function () { for (var e = 0; e < sessionStorage.length; e++) { var t = sessionStorage.key(e); 0 == t.indexOf(_persistenceKey) && (sessionStorage.removeItem(t), e--) } }, this.setItem = function (e, t) { void 0 == t && (t = e, e = _defaultKey), sessionStorage.setItem(_persistenceKey + e, JSON.stringify(t)) }, this.getItem = function (e) { return void 0 == e && (e = _defaultKey), JSON.parse(sessionStorage.getItem(_persistenceKey + e)) }, this.removeItem = function (e) { void 0 == e && (e = _defaultKey), sessionStorage.removeItem(_persistenceKey + e) }) } catch (e) { } this.isAvailable = function () { return e } }, window.Persistence_windowKey = function (e) { var t = window[e], i = !1; "object" == typeof t && (i = !0, this.clear = function () { t[_persistenceKey] = {} }, this.setItem = function (e, i) { void 0 == i && (i = e, e = _defaultKey), t[_persistenceKey][e] = i }, this.getItem = function (e) { return void 0 == e && (e = _defaultKey), t[_persistenceKey][e] || null }, this.removeItem = function (e) { void 0 == e && (e = _defaultKey), delete t[_persistenceKey][e] }, void 0 == t[_persistenceKey] && this.clear()), this.isAvailable = function () { return i } }, window.Persistence = new Persistence_sessionStorage, Persistence.isAvailable() || (window.Persistence = new Persistence_windowKey("py")), !Persistence.isAvailable()) { var titleStartIndex = window.location.toString().indexOf("title"), titleContentIndex = window.location.toString().indexOf("main", titleStartIndex); titleStartIndex > 0 && titleContentIndex > 0 && titleContentIndex - titleStartIndex < 10 && (window.Persistence = new Persistence_windowKey("qt")) } }
</script>

{{#Title}}<h3 id="myH1">{{Title}}</h3>{{/Title}}
{{#Question}}<p>{{Question}}</p>{{/Question}}

<div class="tappable">
    <table style="border: 1px solid black" id="qtable"></table>
</div>

<div class="hidden" id="Q_solutions">{{Answers}}</div>
<div class="hidden" id="Card_Type">{{QType (0=kprim,1=mc,2=sc)}}</div>


<script>
    // Generate the table depending on the type.
    function generateTable() {

        // Options are modified according to user's meta.json in the addon's folder
        const OPTIONS = {
            maxQuestionsToShow: 0
        };

        var type = document.getElementById("Card_Type").innerHTML;
        var table = document.createElement("table");
        var tbody = document.createElement("tbody");

        if (type == 0) {
            tbody.innerHTML = '<tr><th>yes</th><th>no</th><th></th></tr>';
        }

        stripHtmlTagsFromSolutionString();

        let solutions = getCorrectAnswers();
        let questionTableHtmlLinesToSolution = []
        for (var i = 0; true; i++) {
            if (document.getElementById('Q_' + (i + 1)) != undefined) {
                if (document.getElementById('Q_' + (i + 1)).innerHTML != '') {
                    var html = [];

                    let answerText = document.getElementById('Q_' + (i + 1)).innerHTML;
                    let labelTag = (type == 0) ? '' :
                        '<label for="inputQuestion' + (i + 1) + '">' + answerText + '</label>';
                    let textAlign = (type == 0) ? 'center' : 'left';

                    html.push('<tr>');
                    var maxColumns = ((type == 0) ? 2 : 1);
                    for (var j = 0; j < maxColumns; j++) {
                        let inputTag = '<input id="inputQuestion' + (i + 1) +
                            '" name="ans_' + ((type != 2) ? (i + 1) : 'A') +
                            '" type="' + ((type == 1) ? 'checkbox' : 'radio') +
                            // TODO: I don't see how these values are used, please add a comment
                            '" value="' + ((j == 0) ? 1 : 0) + '">';
                        html.push(
                            '<td onInput="onCheck()" style="text-align: ' + textAlign + '">' + inputTag +
                            labelTag +
                            '</td>');
                    }
                    if (type == 0) {
                        html.push('<td>' + answerText + '</td>');
                    }
                    html.push('</tr>');

                    questionTableHtmlLinesToSolution.push({
                        html: html.join(""),
                        solution: solutions[i]
                    });
                }
            } else {
                break;
            }
        }
        let shuffledQuestionTableHtmlLinesToSolution = getShuffledQuestionTableHtmlLinesToSolution(
            questionTableHtmlLinesToSolution, type, OPTIONS['maxQuestionsToShow']);

        let shuffledHtmlLines = shuffledQuestionTableHtmlLinesToSolution
            .map(o => o.html)
            .join("");
        let shuffledSolutions = shuffledQuestionTableHtmlLinesToSolution
            .map(o => o.solution);

        tbody.innerHTML += shuffledHtmlLines;

        table.appendChild(tbody);
        document.getElementById('qtable').innerHTML = table.innerHTML;

        storeCorrectAnswersInHtml(shuffledSolutions)

        onCheck();  // store user answers at least once in case nothing was ticked
    }

    function stripHtmlTagsFromSolutionString() {
        let solutionString = document.getElementById("Q_solutions").innerHTML;
        document.getElementById("Q_solutions").innerHTML = solutionString.replace(/(<([^>]+)>)/gi, "");
    }

    /**
     * Returns the shuffled and potentially reduced lines of HTML combined with their solution value
     *
     * In case of single choice it is guaranteed that the correct answer is shuffled in
     * between the wrong ones.
     *
     * @param   {questionTableHtmlLinesToSolution}  Array[Object]   objects containing the
     *                                                              properties 'html' and 'solution'
     * @param   {type}                              int             Card type
     * @param   {maxQuestionsToShow}                int             see config options
     */
    function getShuffledQuestionTableHtmlLinesToSolution(
        questionTableHtmlLinesToSolution, type, maxQuestionsToShow) {

        if (type != 2) {
            shuffledQuestionTableHtmlLinesToSolution = questionTableHtmlLinesToSolution.sort(
                () => Math.random() < 0.5 ? -1 : 1);

            if (maxQuestionsToShow > 1) {
                return shuffledQuestionTableHtmlLinesToSolution.slice(0, maxQuestionsToShow);
            } else {
                return shuffledQuestionTableHtmlLinesToSolution;
            }

        } else {
            // To have the single correct answer randomly inserted into the wrong ones
            let correctAnswer = questionTableHtmlLinesToSolution.find(o => o.solution == 1);
            let wrongAnswers = questionTableHtmlLinesToSolution.filter(o => o.solution == 0);
            wrongAnswers.sort(() => Math.random() < 0.5 ? -1 : 1);

            if (maxQuestionsToShow > 1) {
                wrongAnswers = wrongAnswers.slice(0, maxQuestionsToShow - 1);
            }

            let randomIndex = Math.floor(Math.random() * (wrongAnswers.length + 1));
            wrongAnswers.splice(randomIndex, 0, correctAnswer);

            return wrongAnswers;
        }
    }

    /**
     * Returns true if the option box/circle is checked.
     *
     * In case of kprim the second box is used as reference.
     *
     * @param   {HTMLTableRowElement}    optionRow    Row containing option boxes/circles.
     * @param   {number}    index   Index of the option in question.
     */
    function isOptionChecked(optionRow, index) {
        return optionRow.getElementsByTagName("td")[index].getElementsByTagName("input")[0].checked
    }

    function getUserAnswers() {
        let type = document.getElementById("Card_Type").innerHTML;
        let qrows = document.getElementById("qtable").getElementsByTagName('tbody')[0].getElementsByTagName("tr");
        let userAnswers = [];
        for (let i = 0; i < qrows.length; i++) {
            if (type == 0 && i == 0) {
                i++; // to skip the first row containing no checkboxes when type is 'kprim'
            }
            if (type == 0) {
                if (isOptionChecked(qrows[i], 0)) {
                    userAnswers.push(1);
                } else if (isOptionChecked(qrows[i], 1)) {
                    userAnswers.push(0);
                } else {
                    userAnswers.push(2);
                }
            } else {
                if (isOptionChecked(qrows[i], 0)) {
                    userAnswers.push(1);
                } else {
                    userAnswers.push(0);
                }
            }
        }
        return userAnswers
    }

    /**
     * Get the solutions stored in the hidden div with id "Q_solutions" as Array.
     */
    function getCorrectAnswers() {
        let solutions = document.getElementById("Q_solutions").innerHTML.split(" ").map(string => Number(string));

        return solutions;
    }

    function storeCorrectAnswersInHtml(solutions) {
        document.getElementById("Q_solutions").innerHTML = solutions.join(" ");
    }

    /**
     * On checking an option this collects and stores answers in between front/back of the card.
     *
     * In case of kprim only the first box is looked at, if it isn't checked the second box has to be.
     * By default a '1' in the answers stands for 'yes' which is the first option from the left.
     */
    function onCheck() {
        // Send question table and encoded answers to Persistence along with the provided solutions
        if (Persistence.isAvailable()) {
            Persistence.clear();
            Persistence.setItem('user_answers', getUserAnswers());
            Persistence.setItem('Q_solutions', getCorrectAnswers());
            Persistence.setItem('qtable', document.getElementById("qtable").innerHTML);
        }
    }

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function tickCheckboxOnNumberKeyDown(event) {
        const keyName = event.key;

        let tableBody = document.getElementById("qtable").getElementsByTagName('tbody')[0];
        var tableRows = tableBody.getElementsByTagName("tr");

        if (0 < +keyName && +keyName < 10) {
            let tableData = tableRows[+keyName - 1].getElementsByTagName("td")[0];
            let tableRow = tableData.getElementsByTagName("input")[0];
            tableRow.checked = !tableRow.checked;
            onCheck();
        }
    }

    // addCheckboxTickingShortcuts is an easy approach on using only the keyboard to toggle checkboxes in mc/sc.
    //
    // Naturally the number keys are an intuitive choice here. Unfortunately anki does capture those.
    // So the workaround is to hold the (left) 'Alt' key and then type the corresponding number to toggle the row.
    function addCheckboxTickingShortcuts() {
        document.addEventListener('keydown', tickCheckboxOnNumberKeyDown, false);
    }

    function isMobile() {
        if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
            return true;
        } else {
            return false;
        }
    }

    function run() {
        // for previewing the cards in "Manage Note Type..."
        let DEFAULT_CARD_TYPE = 1;
        let DEFAULT_SOLUTIONS = "1 0 0 0 0";

        if (isNaN(document.getElementById("Card_Type").innerHTML)) {
            document.getElementById("Card_Type").innerHTML = DEFAULT_CARD_TYPE;
        }
        if ('{' + '{Answers}' + '}' == document.getElementById("Q_solutions").innerHTML) {
            document.getElementById("Q_solutions").innerHTML = DEFAULT_SOLUTIONS;
        }

        if (document.getElementById("Card_Type").innerHTML != 0 && !isMobile()) {
            addCheckboxTickingShortcuts();
        }

        setTimeout(generateTable(), 1);
    }

    async function waitForReadyStateAndRun() {
        for (let i = 0; i < 100; i++) {
            if (document.readyState === "complete") {
                run();
                break;
            }
            console.log("Document not yet fully loaded (readyState: " + document.readyState + "). Retry in 0.1s.");
            await sleep(100);
        }
    }

    /*
    The following block is inspired by Glutanimate's Cloze Overlapper card template.
    The Cloze Overlapper card template is licensed under the CC BY-SA 4.0
    license (https://creativecommons.org/licenses/by-sa/4.0/).
    */
    if (document.readyState === "complete") {
        run();
    } else {
        waitForReadyStateAndRun();
    }
</script>

It seems like the AllInone card template doesn’t exist and Anki is using the default one, but the note template is apparently right:

Again, apart from the scripts, the fields used in the template are Title, Question, and Answers, and I think the final field is controlled by the scripts.

But there are no such fields in the note, according to your screenshot. You have fields named Pregunta, Tipo de pregunta, Respuesta 1, and so on.

Your template doesn’t match the fields of this note type at all. It’s not clear how this could possibly work.

YEs, I agree, but I don’t know why before the updating the addon runs perfectly (Anki linked the note template with its correspondent card) and now the app doesn’t find the AllInone card template as before.

If it could help in something, this is the original card template. I just changed the names of the fields to Spanish and so on:

Renaming the fields broke the template.

Normally, when you rename the fields of a note type, the names are automatically changed in the template as well. I don’t know why this didn’t happen here.

So you should edit your Front template to change
{{Question}} to {{Pregunta}}
{{#Question}} to {{#Pregunta}}
{{/Question}} to {{/Pregunta}}

and similarly change Answers to Plantilla respuestas correctas

What happened to the Title field? Did you delete it instead of renaming it? You should either edit or delete that line in the template as well.

Finally, the template contains 'Q_' in three different places. However, you renamed Q_1 to Respuesta  1 and so on, so this part won’t work.

I don’t think a space is a legal character for ID names in HTML, so to allow the JavaScript code to work, you’d have to rename those fields to Respuesta_1 with an underscore instead of a space, and Respuesta_2 and so on, and then change 'Q_' everywhere in your template to 'Respuesta_'

But in older versions of Anki, it was working without editing the code. As I said, I change the field names, delete some of them and change the order but, everything was automatically changed and, anyway, the addon have been working fine.
Something changed between the former version and the new one. However, I tried to solve the issue returning to an older one, but I had no success.
So, I will change the template assuring the right linking between field names and I will check the addon performance.

Thanks, a lot, for your help.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.