I have two questions regarding the creation of cards templates on Anki desktop.
Is there still no built-in way to define a custom mechanism (aka custom algorithm) to compare the value entered by a user (via a type in answer’s input on the front side) with the expected value (when revealing the back side)?
Is there still no built-in way to share data between the front and back of a card?For example, suppose on the front of the card I generate data via a JavaScript function. How can I retrieve the data on the back of the card?
By built-in, I mean without using add-ons.
As a matter of fact, these questions seem to have been raised in the past years, but I can’t find any solution provided yet… For example :
Yeah, I already saw those links. But the solutions provided there don’t quite qualify as built-in to me… Also,I have mixed feelings on the usage of localStorage as featured on the reddit’s post
I can now pass the user’s input from the front to the back of the card and provide my own verification algorithm on the back using a JavaScript function.
But I’m curious. When searching, I couldn’t find the solution you provided anywhere. Perhaps I didn’t look in the right place, or is this a recent undocumented change? Has this solution always worked or is it more or less recent, specifically for Anki desktop?
Has this solution always worked or is it more or less recent, specifically for Anki desktop?
I have no clue! Most people go straight to session storage solutions and don’t bother thinking of alternatives. I only found this solution after following the new study screen development and testing some things. I have shared it on Discord before, but that doesn’t show up in search results.
Besides, this solution only works for temporary data from the back of a card to the next. Though, you can use something like the code below to keep a single variable across multiple cards until you leave the study screen:
For the sake of completeness, I think sessionStorage method should also be mentioned explicitly. It is much simpler than the reddit post might imply. Just use
sessionStorage.setItem("a", a);
to save data and
sessionStorage.getItem("a");
to retrieve it. Wrap it in Number() for numeric values or in JSON.parse() for general kind of objects.
The anki persistence script basically does that exact thing, just making some utility functions built on top of the sessionStorage. Although, in many cases it might be more convenient to use the latter directly.
Regarding defining variables in the global scope, it should be noted that it probably won’t work on AnkiMobile. On AnkiDroid, the old study screen, to my knowledge, does not have all the functionality of the old one yet. And as for the desktop, at some point dae mentioned that
which might mean that global variables are not be the most future-proof method in general. (Although, who knows how those plans were affected by the current state of things).
I’m lucky that you found that solution(special mention for its simplicity) and saw and answered my message here.
I think the fact that it only works for temporary data covers most cases. But I’m keeping your code for persistence across multiple cards handy, it may come in useful one day…
In fact, this was one of the first solutions that came to mind. But I couldn’t quite understand how the content ‘above the arrow’ (which is supposed to represent user input) was generated, with the sequence of tags having the classes ‘typeGood’, “typeBad”… I think it’s called “diff match patch”? In particular, there is the case where dashes are added by the algorithm in the text ‘above the arrow’.
In the end, it all seemed a bit fragile and cobbled together, so I gave up on that idea.
Regarding your code, it seems to me that the ‘span’ tags with the class “typeMissed” never appear ‘above the arrow’, so I don’t think the replace function has any effect. Moreover, after testing, and unless I am mistaken, the content of the variable ‘my_ans’ before and after applying the replace function seems to be the same.
Your first idea seems simpler anyway, so I think I’ll stick with it for now.
It appears when you “miss” a few characters, for example, when you enter “nki”, but the answer is “Anki”, the “A” will be shown as
<span class="typeMissed">-</span>
More “-” are used if you missed more characters, thus the .*? regex. The dashes are not part of the user’s answer so I removed them. Incorrect (“bad”) characters are just wrapped by a span with a typeBad class, which gets stripped with the other HTML.
The main reason I was always reluctant to use that stuff is because it was in a GitHub repo which felt beyond my skill set. Now, thanks to your comment, I learned that sessionStorage and localStorage are more general JavaScript “things”: Window sessionStorage Property.
I hear the concern…But in the first solution provided by @ZornHadNoChoice, the scope of the variable is the front and back of the card. So at least in that case, it should be future proof since it is not the same scope as global, or is it?
It is. Right now the desktop Anki loads the reviewer html page once reviewing is initiated and then just completely replaces innerHTML of the #qa element in it each time an answer is revealed or a new card is opened.
As such, all the scripts used in the templates are inserted directly into the main page’s code and are executed in the global scope of the reviewer, which is exactly the reason why the values of variables are accessible after flipping a card. This also means you can’t use let x = ... or const x = ... in the root of the script – it will cause an error when a second card will try to redefine an already defined variable/constant (var x = ... or just x = ... are fine though).
AnkiWeb does the similar thing, and, if @ZornHadNoChoice is correct about the new study screen, AnkiDroid seems to be starting to do it too. The old study screen (and, iirc, AnkiMobile), on the other hand, reloads the whole reviewer page on each card flip. If the isolation in iframes is implemented in the future (which is a sane thing to do, from security and general reliability point of view), this would mean that the desktop version will effectively do the same thing as well (each iframe has its own scope, so variable values won’t persists between them). This does not necessarily mean that back and front side of the same card won’t be placed into the same iframe (just like they are placed into the same global window right now). But I would assume, whichever approach the desktop version takes, will be carried over to other platforms afterwards.
It’s mostly just one tricky css selector and a bit of post-processing:
#typeans span targets all spans inside the id="typeans" element, while :not(.typeMissed) automatically skips those that have the typeMissed class. Similarly, :not(br ~ span) filters out those of them that follow a <br> element, meaning the result only contain the spans before the first <br>, which is what corresponds to the user-typed part.
querySelectorAll yields the list of all nodes matching the selector, which is then converted into a regular array using [...nodeList], so that .map method could be called from it.
.map itself iterates over all array elements, returning an array of the results from applying the specified function to each original element.
L=>L.innerText is an arrow function, basically a short form of writing