Skip to content

Runtime Execution Context

In JavaScript, the global object varies depending on the execution environment. In the browser, the global object is window, which includes properties like document. In Node.js, the global object is global. This object is specific to Node.js and is not available in browsers.

For example, you cannot access process.env in a browser environment because it is specific to Node.js. Similarly, calling document.createElement on a server does not work, as it is intended for the client-side. Attempting either will result in a runtime error.

In Open Data Capture, code runs on both the client and server. You should therefore not access browser-specific APIs, like document or window unless you are within functions named render, which execute exclusively on the client. For those familiar with Next.js, think of these functions as rendering a component with a “use client” directive within a React Server Component.

Example of Problematic Instrument

import { defineInstrument } from '/runtime/v1/@opendatacapture/runtime-core';
import { z } from '/runtime/v1/zod@3.23.x';
const button = document.createElement('button');
button.textContent = 'Submit Instrument';
document.body.appendChild(button);
export default defineInstrument({
// ...
kind: 'INTERACTIVE',
content: {
render(done) {
button.addEventListener('click', () => {
done({ message: 'Hello World' });
});
}
}
// ...
});

Fixing This Instrument

To fix the instrument, there are two options:

  1. Move the browser-specific code directly within the render function
  2. Wrap the browser-specific code in a function. This function can be defined outside the render function but must only be called within it.

Example

import { defineInstrument } from '/runtime/v1/@opendatacapture/runtime-core';
import { z } from '/runtime/v1/zod@3.23.x';
function createButton() {
const button = document.createElement('button');
button.textContent = 'Submit Instrument';
document.body.appendChild(button);
return button;
}
export default defineInstrument({
// ...
kind: 'INTERACTIVE',
content: {
render(done) {
const button = createButton();
button.addEventListener('click', () => {
done({ message: 'Hello World' });
});
}
}
// ...
});