= 23 myMessage
Inject OJS variable from vanilla JavaScript
Make something you load with vanilla JavaScript be accessible as an OJS variable
Most JavaScript libraries work just fine in OJS. But for a current project I want to use a framework that needs to be loaded in vanilla JavaScript (because we want to make a Quarto extension that uses it, and we can’t inject OJS code into a Quarto doc without considerable work).
The library has a constructor function, and the object it sends back can be configured to have callbacks that respond to browser events. I want to rig those callbacks so that they update an OJS variable—that way, users of our extension can access its state and use it to drive visualisations.
So the question is, can we declare and update an OJS variable from vanilla JavaScript?
I can see that window._ojs
exists, and that window._ojs.ojsConnector
has a mainModule
that appears to (further down) contain variables being declared in my OJS blocks. For example, here’s an OJS variable, ‘myMessage’:
if I run this vanilla JS in the browser devtools:
const ojsVars = window._ojs.ojsConnector.mainModule._scope.entries()
const var1 = ojsVars.next().value
0]
var1[// "myMessage"
1]._value
var1[// 23
Great! There may be a problem checking that the runtime and module have loaded, but we can at least see it.
The OJS runtime docs describe the process of attaching a variable to a module (which is a namespace for variables), and a module to a runtime (an instance of OJS, I think) as something like:
const runtime = new Runtime(builtins);
const module = runtime.module();
const a = module.variable();
// define as a constant
.define("foo", 42);
a
// define another variable that takes an argument
const b = module.variable();
.define(["foo"], foo => foo * 2); b
Okay, let’s try to register a new value using the Quarto OJS connector module:
<script>
// wait 'til dom content is loaded before trying to touch the ojs module
addEventListener("DOMContentLoaded", (event) => {
console.log("DOM content loaded, checking for OJS module")
// check for the ojs connector first
const ojsModule = window._ojs?.ojsConnector?.mainModule
if (ojsModule === undefined) {
console.error("Quarto OJS module not found")
else {
} console.log("Quarto OJS module found!")
}
// register the var
const myVar = ojsModule.variable();
.define("myVar", () => {
myVarasync function* myGen() {
yield 1;
while (true) {
yield Promises.delay(1000, Math.random());
}
};
})
// let's try a different strategy:
// calling variable.define() on the timer instead
const anotherVar = ojsModule.variable();
.define("anotherVar", 23);
anotherVar
setInterval(() => {
.define("anotherVar", Math.random());
anotherVar, 1000);
}
;
})
</script>
What’s the value of myVar
? It’s ! And anotherVar
is .
console.log("myVar is " + myVar)
console.log("anotherVar is " + anotherVar)
Okay, so I’m able to call variable.define()
on a timer and just keep updating it to a new constant (that’s what anotherVar
is doing), but I’m having trouble defining the variable once to use a generator (which could potentially improve performance, but I’m not sure), as in myVar
.
I think the anotherVar
strategy should be enough for me: I’ll call variable.define
inside the callback for our framework and we’ll be cookin’ with gas 🥳