Skip to content

How the live editor works

Pamela Fox edited this page Feb 13, 2019 · 10 revisions

The basic setup

The live editor consists of two main components:

The code editor is on the left hand side, and by default, it is powered by the ACE editor. Other editors can also be plugged in (like the structured-blocks editor).

The output is on the right hand side in an iframe, and it executes code (either ProcessingJS code, HTML/CSS/JS, or SQL) . On Khan Academy, that iframe is on a separate domain so that users can't access Khan Academy user credentials. Be aware of that if running this on an authenticated domain.

Diagram of live editor

Everything is kicked off by instantiating a new LiveEditor() on a page, giving it a div to live inside, and specifying paths if necessary:

var options = {
    el: $("#sample-live-editor"),
    execDir: "../../js/exec/",
    execFile: "../../exec.html",
    externalsDir: "../../external/",
    imagesDir: "../../images/"
};

window.liveEditor = new LiveEditor(options);

The life cycle of a code edit

This is the life cycle of a code edit for the ProcessingJS environment, there are some differences in HTML/CSS and SQL:

  1. When the user types in code, that triggers the ACE editor's "change" event. See LiveEditor.bind in live-editor.js
  2. LiveEditor posts a message to the exec iframe with the current code. See LiveEditor.runCode in live-editor.js
  3. When the exec iframe receives a message with code in it, it will send it through a series of steps to check for errors. See Output.runCode in output.js
  4. It will first send the code to the JSHint worker. That worker will respond back with any encountered errors.
  5. It will then send the code through BabyHint (not in a worker, as it's not super intensive), and will merge together JSHint errors and BabyHint errors.
  6. It will then send the code and any associated tests (like for coding challenges) to the test worker.
  7. If there are hint-related errors, it will display them using Output.handleError. Otherwise, it will proceed to sending it to the associated output mechanism.
  8. When the code gets sent to PJSOutput, it goes through a ProcessingJS-specific process. See PJSOutput.runCode in output.js
  9. It first caches any images found in the code using the PJSResourceCache and executes the code using PJSOutput.injectCode
  10. The CodeInjector should have wrapped LoopProtector around any loops, so it will pop up a runtime error after a few seconds if it perceives long-running loops.

Deep dive on the output components

JSHint Worker

See js/workers/pjs/jshint-worker.js, external/jshint/jshint.js, external/es5-shim/es5-shim.js

The JSHint worker delay loads its dependencies (ES5-shim and JSHint), due to the need on Khan Academy to communicate the user's language. Our JSHint is a custom fork both because it has translated message strings and has more friendly, helpful messages.

BabyHint

See js/output/pjs/babyhint.js

The BabyHint library does an additional series of checks that JSHint does not do:

  • It checks for misspellings of functions/variable names, and throws an error suggesting the correct spelling based on ProcessingJS keywords and program variables.
  • It throws an error if the user from using banned properties like document and location.
  • It throws an error if the user declares a function in the function name(){} style instead of var name=function(){}, as we only support the latter.
  • It checks for trailing equal signs, a common error.
  • It checks for no whitespace after var and before a variable name, a common error.

Test Worker

See: js/workers/pjs/test-worker.js, js/output/shared/output-tester.js, external/structuredjs/structured.js

This worker is used for checking user code against specified tests. This is used in the coding challenges in the Khan Academy curriculum, to help the student practice what they've learnt and get feedback about how well they're doing.

The tests are written using an interface defined by OutputTester in output-tester.js, and detailed in this guide for challenge creators. Basically, they look for particular structures in the code using the StructuredJS library, and decide to either accept the code or give a hint about what's wrong with the particular structure.

Once the worker gets the results from OutputTester, it will return an array of test results and any errors encountered along the way.

ProcessingJS Code Injection

See: js/output/pjs/pjs-output.js

Once the code has gone through all the checks and is error-free, it's ready to be executed by Output.injectCode. If it's the first time the code has been injected, it's just executed normally. If there's a code change and another call to Output.injectCode, then the function decides what aspects of the code to dynamically inject. That way, users can change the contents of the draw function and just see the effect of that on the current state of animation.

For more details, watch this talk from EmpireJS or read the extensive comments in Output.injectCode.