HTML Controls Setup Guide
This guide provides an overview over the usage of the JavaScript API to create an external webpage that can be used to create a custom show flow that interacts with OpenSpace.
More examples of button pages are available at ui.openspaceproject.com. The code for these pages are found in this GitHub repository.
Required files
For reference, these are the files being explained in this guide:
The OpenSpace JavaScript API:
openspace-api.jsStyle sheet for the page:
main2.cssHTML page:
openspace_control_template.html
Setting up the HTML document
We typically have most of the controls in the head of the HTML document.
<html>
<head>
<title>Basic OpenSpace Controls</title>
<link rel="stylesheet" type="text/css" href="main2.css">
<script type="text/javascript" src="openspace-api.js"></script>
<script type="text/javascript">
// Variable for js libarary
var openspace = null;
...
Starting with title:
<title>Basic OpenSpace Controls</title>Link to stylesheet:
<link rel="stylesheet" type="text/css" href="main2.css">Include OpenSpace JavaScript API code:
<script type="text/javascript" src="openspace-api.js"></script>And finally opening another script block in which we will define all our buttons and functions
Then we usually start by defining a few useful functions we may use later, such as:
// Helper function to set the focus target
function setFocus(focus) {
openspace.setPropertyValue('NavigationHandler.OrbitalNavigator.Anchor', focus);
openspace.setPropertyValue('NavigationHandler.OrbitalNavigator.RetargetAnchor', null);
}
We can now start writing the buttons. As an example I’ll start with an object called earthButtons with two properties, “title” and “buttons”.
let earthButtons = {
title: "Example buttons for Earth",
buttons: {
}
};
We can define as many of these sections as needed to organize the buttons. The actual buttons will then be key value pairs where the string to be displayed on the button is the key and a corresponding arrow function to be executed on click is the value. As a simple example a button to turn off the model of the earth would be as follows:
let earthButtons = {
title: "Example buttons for Earth",
buttons: {
'Turn off the Earth Model': () => { openspace.setPropertyValueSingle('Scene.Earth.Renderable.Enabled', false) },
},
};
We can also add a button which uses one of the helper functions defined earlier:
let earthButtons = {
title: "Example buttons for Earth",
buttons: {
'Turn off the Earth Model': () => { openspace.setPropertyValueSingle('Scene.Earth.Renderable.Enabled', false) },
'Focus on The Moon': () => { setFocus('Moon') },
},
};
To get these buttons displayed on the page we first define a new array with all the names of each section, in this case we only have the earthButtons section. We will then define a function to map the JavaScript to HTML:
const buttonGroups = [earthButtons, otherButtons];
// Helper function to map the buttons to html
function mapButtons(openspace) {
buttonGroups.map((action, id) => {
let cardHTML = "<div class='card'><h2>" + action.title + "</h2>";
if (action.description) {
action.description.split('\n').map(item => {
cardHTML += "<p>" + item + "</p>";
});
}
if (action.buttons) {
Object.keys(action.buttons).map(button => {
const fn = action.buttons[button];
cardHTML += '<button data-id="' + button + '" onClick="(' + fn + ')(event)">' + button + '</button>';
});
}
cardHTML += "</div>";
document.getElementById('main').innerHTML += cardHTML;
});
}
In this code the mapButtons function generates an HTML card for each section provided in the buttonGroups array. Each card will contain a title (taken from the title property), buttons, and an optional description (not shown in this example). The function iterates over the key value pairs in each section and generates an HTML button for each one using the key as a label and calling the corresponding function on click. Each button will also be given a data-id equal to the button label which will be useful for styling individual buttons.
Next, we will also need a function to connect to OpenSpace:
// Helper function to connect to opensapce
let connectToOpenSpace = () => {
// Setup the api params
let host = document.getElementById('ipaddress').value;
if (!host) {
// Use localhost as default, if no ip is entered
host = 'localhost';
}
let api = window.openspaceApi(host, 4682);
// Notify users on disconnect
api.onDisconnect(() => {
console.log("disconnected");
document.getElementById('container').className = "disconnected";
let disconnectedString = "Connect to OpenSpace: ";
disconnectedString += '<input id="ipaddress" type=text placeholder="Enter ip address" /> ';
disconnectedString += '<button onClick="connectToOpenSpace();">Connect</button>';
document.getElementById('connection-status').innerHTML = disconnectedString;
openspace = null;
});
// Notify users and map buttons when connected
api.onConnect(async () => {
try {
document.getElementById('container').className = "connected";
document.getElementById('connection-status').innerHTML = "Connected to OpenSpace";
openspace = await api.library();
console.log('connected');
mapButtons(openspace);
} catch (e) {
console.log('OpenSpace library could not be loaded: Error: \n', e)
return;
}
})
// Connect
api.connect();
};
We can then close out the document and define more elements to enter an IP address when disconnected.
</script>
</head>
<body>
<!-- HTML Containers -->
<div id="container" class="disconnected">
<div id="connection-status" class="connection-status">
Connect to OpenSpace:
<input id='ipaddress' type=text placeholder="Enter ip address" />
<button onClick="connectToOpenSpace();">Connect</button>
</div>
<div id="main">
</div>
<script type="text/javascript">
connectToOpenSpace();
</script>
</div>
<body>
</html>
Here we are also creating an empty div with id=main in which the mapButtons function will populate the generated buttons. Once opened in a browser (and with OpenSpace running on the same machine) it should look something like this:

This will of course also depend on the CSS, the file we are using is linked at the top of this document.
How to get OpenSpace commands from the script log
An easy way to generate the commands to assign to buttons is to first perform the sequence using the GUI then add the output of the script log to a button. The script log can be found in the OpenSpace directory under OpenSpace-0.X.X/logs/ScriptLog.txt.
For example, to setup a scene showing the April 8th, 2024 eclipse in the OpenSpace GUI you might:
Jump to Earth at a specific location
Set time to April 8th
Set the visible layer to Blue Marble
Speed up time to view the path of the eclipse
The ScriptLog would then contain:
return openspace.navigation.jumpToGeo("Earth", "33.1", "-106.9", "6000000")
return openspace.time.setTime("2024-04-08T17:30:13.184")
openspace.setPropertyValueSingle("Scene.Earth.Renderable.Layers.ColorLayers.ESRI_VIIRS_Combo.Enabled", false)
openspace.setPropertyValueSingle("Scene.Earth.Renderable.Layers.ColorLayers.Blue_Marble.Enabled", true)
return openspace.time.interpolateDeltaTime(60)
We then add these commands to a button to easily return to this scene. First, taking everything after “return” and replacing the double quotes with single quotes, then adding the commands to an arrow function:
'Setup Eclipse Scene': () => {
openspace.navigation.jumpToGeo('Earth', '33.1', '-106.9', '6000000')
openspace.time.setTime('2024-04-08T17:30:13.184')
openspace.setPropertyValueSingle('Scene.Earth.Renderable.Layers.ColorLayers.ESRI_VIIRS_Combo.Enabled', false)
openspace.setPropertyValueSingle('Scene.Earth.Renderable.Layers.ColorLayers.Blue_Marble.Enabled', true)
openspace.time.interpolateDeltaTime(60)
},
Conditional formatting
Since we have access to the OpenSpace API, we can check the value of various properties and use their status to conditionally format different buttons. We find this most useful for toggles, and overall styling to reflect if the webpage is connected to OpenSpace.
To achieve this we add a new block of JavaScript code toward the end of our document. In this script block we define several arrays and functions which we will use toget the current property value and update the button styling based on the returned value:
let api = window.openspaceApi('localhost', 4682);
let intervalIds = [];
api.onConnect(function () {
// Declaring the toggles Object with key value pairs, the keys are the button names you want to be updated
// The values are the corresponding openspace properties to get the value for
const toggles = {
'Turn off the Earth Model': 'Scene.Earth.Renderable.Enabled',
'Jupiter Trail Toggle': 'Scene.JupiterTrail.Renderable.Enabled',
};
// Creating empty array for intervalIds generated by using setInterval so I
// can stop the loop onDisconnect
intervalIds = [];
// The button-updating function: Takes in the button id to be updated and the
// Openspace Property to get the value of.
// This was adapted from an example from the Openspace team
async function updateButtonStyle(buttonId, propertyId) {
if (!openspace) {
console.log("OpenSpace library not loaded yet");
return;
}
const enabled = await openspace.propertyValue(propertyId);
const button = document.querySelectorAll(`[data-id=${CSS.escape(buttonId)}]`);
// Update the button's CSS style based on if the property is enabled
if (enabled === true) {
button.forEach(function(item) {
item.classList.add('ButtonGreen');
item.classList.remove('ButtonRed');
});
} else {
button.forEach(function(item) {
item.classList.add('ButtonRed');
item.classList.remove('ButtonGreen');
});
}
}
// Loooping through each key value pair in the toggles object. The key is the
// button id and value is the identifier for the corresponding openspace property
Object.entries(toggles).forEach(([key, value]) => {
// Using setInterval to call the function once per second
const intervalId = setInterval(function() {
updateButtonStyle(key, value);
}, 1000);
intervalIds.push(intervalId); // pushing the intervalId to the array for stopping the loop later and avoiding errors
});
});
api.onDisconnect(function() {
console.log("Disconnected");
intervalIds.forEach(clearInterval); // clearing the Intervalids to stop the topic subscriptions
});
api.connect();
With all these additions the control page now looks like this:

Here the two buttons are highlighted based on the property they change, to be more color-blind friendly we have set it to orange for ON and red for OFF.