Updated 2016-10-17 to work with AnkiDroid!

Introduction

Anki is brilliant. It has really lived up to its motto ‘Remember Anything, Remember Anywhere, Remember Efficiently’ for being my go-to way to remember notes and other information. This is made all the more powerful through its HTML-based card system, which includes support for complex behaviour via JavaScript. Unfortunately, utilising JavaScript to perform such behaviour is less than straightforward.

Fortunately, I've done all the hard work for you, so here's a guide on using JavaScript in Anki. I used it to generate physics questions and associated solutions, but the general idea should be applicable to any use case.

Getting JavaScript into Anki

Although the ‘Add Note’ screen in Anki allows HTML and JavaScript input, any JavaScript input here is automatically executed during the editing stage, which is not exactly helpful for creating dynamic content.

Instead, JavaScript needs to be set up from within the card template. To do this, create a new note type by accessing the Add Note screen, then clicking the note type (Basic, in the images above), Manage, Add, Add: Basic, OK.

Give it a name like ‘Script’, then go back to the Choose Note Type window and choose the new note type.

Click the Cards… button, and at the beginning of the Front Template, add some JavaScript. If all goes well, the Front Preview should show the results of the JavaScript code.

Custom Per-Card JavaScript

Now creating a new note type for every single piece of JavaScript you want to add is impractical, so let's find a way to make the note type execute code based on a per-card field. At the beginning of the Front Template, replace the previous JavaScript code with:

var code = (function () {/* {{Script}} */}).toString();
code = code.replace(/^[^\/]+\/\*!?/, "").replace(/\*\/[^\/]+$/, ""); //Strip beginning/ending comments.
code = code.replace(/<div>/g, "\n").replace(/<\/div>/g, "\n").replace(/<br \/>/g, "\n"); //Strip HTML.
code = code.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&"); //Replace special symbols.
eval(code);

The first two lines take the HTML code of a per-card Script field and store it in a JavaScript variable. The third and fourth lines strip extraneous HTML code from the variable, hopefully turning it into executable JavaScript code. Depending on which symbols you use, you may need to add more replacements to the fourth line. The final line executes the JavaScript code.

Right now, the code won't work, because the Script field has not been declared. Close the Card Types for Script window, click the Fields… button, and add a new Script field.

Now, any code in the Script field will be executed each time the card is shown, once for the front, then again later for the back.

In order to access the contents of the card more easily from within JavaScript, some changes can be made to the Front Template and Back Template, giving each side an id attribute:

Note that if you want to access these, you will need to delay the call until after the DOM has finished loading, by wrapping your JavaScript in window.setTimeout(function(){ … }, 0); or something similar.

Data Persistence

Depending on what you want to do, the above technique will work perfectly. More likely, however, is that the fact the JavaScript is executed twice will cause problems for you. The images below show a common scenario, wherein the generated question changes when flipping sides of the card. Not useful for checking your answer!

Thus, we need a way of storing data within the JavaScript code across executions of the same card. Unfortunately, typical ways of doing this, localStorage and document.cookie are disabled in desktop Anki. From a ‘normal’ JavaScript point of view, each execution is entirely separate from the other. (Correction: The window object is shared across invocations and may be an alternative method of storing data.)

Thankfully, Anki exposes a behind-the-scenes window.py object. The internal functions exposed by the object are pretty useless, but the instance of the object is shared between executions of the same card (and reset if the card is shown again later), meaning that it can be used to store data between executions.

AnkiDroid does not appear to expose a similar object, however Android's WebView supports sessionStorage, which we can use to store data persistently.

The code below automatically detects which method to use and exposes an API to store data to overcome the original restriction.

function persist(cb) {
	window.setTimeout(function() {
		// Determine whether to use Anki's Bridge object (Desktop) or sessionStorage (AnkiDroid) to store data across sides.
		var dummy = {};
		var mode = "dummy";
		if (typeof(py) !== "undefined") {
			mode = "py";
			py.data = py.data || {};
		} else if (typeof(sessionStorage) !== "undefined") {
			mode = "sessionStorage";
		}
		
		var dataObj = {
			setItem: function(key, val) {
				if (mode === "dummy") {
					dummy[key] = val;
				} else if (mode === "py") {
					py.data[key] = val;
				} else if (mode === "sessionStorage") {
					sessionStorage.setItem(key, val);
				}
			},
			getItem: function(key, def) {
				var val = undefined;
				if (mode === "dummy") {
					val = dummy[key];
				} else if (mode === "py") {
					val = py.data[key];
				} else if (mode === "sessionStorage") {
					val = sessionStorage.getItem(key);
				}
				if (val == null) {
					return def;
				} else {
					return val;
				}
			},
			tryItem: function(key, val) {
				var currVal = dataObj.getItem(key, undefined);
				if (currVal == null) {
					dataObj.setItem(key, val);
					return val;
				} else {
					return currVal;
				}
			},
			clear: function() {
				if (mode === "dummy") {
					dummy = {};
				} else if (mode === "py") {
					window.py.data = {};
				} else if (mode === "sessionStorage") {
					sessionStorage.clear();
				}
			}
		};
		
		if (!document.getElementById("back")) {
			dataObj.clear();
		}
		
		cb(dataObj);
	}, 0); //Execute after Anki has loaded its Bridge object.
}

We can call it like:

persist(function(data) {
	var num = data.tryItem("num", Math.random());
	
	document.getElementById("front").innerHTML = document.getElementById("front").innerHTML.replace("%NUM%", num);
	
	if (document.getElementById("back")) {
		document.getElementById("back").innerHTML = document.getElementById("back").innerHTML.replace("%NUM", num);
	}
});

Anki+JavaScript in Action