JAVASCRIPT

NOTE: This page is currently a Work In Progress.

 

One of the methods available to developers for creating instruments is to use a combination of HTML, JavaScript and CSS, as well as some optional XML. Together these files are placed in a location within a project (either as part of an aircraft, or as a unique package that can be used by multiple aircraft), and essentially they work together as follows:

  • The HTML provides the "framework" for the gauge
  • The JavaScript provides the logic
  • The CSS provides the styling
  • The XML (panel.xml) provides additional functionality and is referenced from the JavaScript

 

The "glue" that holds all this together is Coherent GT, which is the web engine being used by Microsoft Flight Simulator to display glasscockpits. This allows the simulation to bind its C++ data to the JavaScript used to make the gauges and instruments. Using Coherent means that you can call functions on the C++ side while also listening to events on the JavaScript side. Using Coherent also has the additional benefit that it enables you to move large amounts of data - like setting flightplans, or getting the nearest airports, etc... - which is something that just can't be done using SimVars. Also note that Coherent has a web interface that permits you to debug your instruments, which is explained on the following page:

 

Also note that you can find tutorials for creating HTML/JS/CSS instruments on the following page:

 

Finally, it is worth noting that these sections describe the underlying SDK API for creating gauges using JavaScript, however many aircraft - both custom ones and those from Microsoft - use the MSFS Avionics Framework, which is a component based API specifically designed for making instrumentation using a React-like framework. You can find more information here:

Note that this framework is not covered by the standard SDK documentation as it is already extensively documented at the link above.

 

 

HTML Setup

The most basic setup for your HTML file will look something like this:

<link rel="stylesheet" href="MyGauge.css" />
<script type="text/html" id="MyGauge_Template">    
    <div id="Mainframe">
        <div id="Electricity" state="off">    
        </div>
    </div>
</script>
<script type="text/javascript" src="/Pages/VCockpit/Instruments/MyGauge/MyGauge.js"></script>

This links the CSS stylesheet to use, calls the relevant scripts, and also calls the JavaScript file you have created for the gauge. Now, there are some things to note here:

  • Your HTML will be inserted in the DOM so there is no need to create a regular HTML boilerplate with the usual <html>, <head>, and <body> sections.
  • You can reference your CSS file as you would normally using a <link> tag.
  • Your gauge HTML needs to be inside of a <script> tag which is identified by an ID.
  • The path of your javascript file has to be relative to your Pages folder
  • The "Mainframe" <div> element is used by convention as the highest parent in the DOM, but it is not mandatory, only recommended.
  • The "Electricity" <div> element and the state attribute is also recommended but not mandatory. The JavaScript BaseInstrument class will update the state attribute depending on the electricity received by the glasscockpit.

 

Once you have the files required, you can then reference the instrument in an aircraft through the panel.cfg file, which also defines where it'll be rendered within the virtual cockpit, eg:

[Vcockpit01]
size_mm      = 230,45
pixel_size   = 230,45
texture      = texture
htmlgauge00  = MyGauge/MyGauge.html,0,0,230,45

 

 

Base JavaScript Files

There are a number of JavaScript files that Microsoft Flight Simulator will insert into the instrument context automatically for you, meaning they do not need to be imported by you, and that you can reference and use the functions they contain within your own JS files. These are:

 

  • animation.js - Provides is a small animation library which lets you define animations in sequence, targetting different elements and controlling their playback (note that - behind the scenes - all animations are generated as CSS animations, and that this only works with HTMLElement, and not with SVG). This is generally only useful for UI elements, and not for glasscockpits.

 

  • avionics.js - Provides a few functions to compute distance and angle values, as well as providing altitude and airspeed scrollers. Note that not all the methods in this file are maintained and some may be deprecated. 

 

  • BaseInstruments.js - Provides the flow of the glasscockpit (Init, Update, ConnectedCallback, etc...), and every glasscockpit should inherit from this class.

 

  • coherent.js - Provides the data binding functions to the simulation. Functions like call, trigger, and on are useful to exchange data.

 

  • common.js - Provides a massive number of "common" functions that are used by gauges for multiple different things.

 

  • GenericDataListener.js - Provides methods that can be used to exchange json data betwen Coherent views.

 

  • simplane.js - Provides calls for SimVar but with an added cache, ie: instead of calling a SimVar directly, you call a method like getTrueAirspeed() which calls the SimVar with a cache added to it for better performance. In general we recommend that you use the simvar.js methods or call the SimVar directly rather than use the methods in this file.

 

 

  • Types.js - Provides some type definitions for SimVar structures. For example, instead of getting a number from a SimVar, you can get more complex structures like {lat, long, alt}.

 

  • VCockpit.js - Manages all the registered glasscockpits, and binds the flow of a glasscockpit from the simulation to BaseInstrument.

 

 

These files (and many others) can be found in the following location:

\Packages\Official\Steam\asobo-vcockpits-instruments\html_ui\Pages\VCockpit\Instruments\Shared
// OR
\Packages\Official\OneStore\asobo-vcockpits-instruments\html_ui\Pages\VCockpit\Instruments\Shared

Note that you may also use any of the other shared JS files from that location in your own projects as well, however they will need to be specifically imported into the gauge context.

 

Due to the massive number of methods and files available here, the documentation will not be covering everything and it is assumed you have a good enough working knowledge of JavaScript to be able to study the files and work out what the majority of them do. However, we do have a couple of pages that cover the most important and essential classes and methods, since they will be the ones you have to use and extend on when creating a gauge:

 

 

 

Simple JavaScript Instrument Example

The following is a full example for a very simple JavaScript instrument that will be used for displaying the time. It shows the contents of each of the component files used by the instrument, and illustrates the way they all work together.

 

The JavaScript

The following JavaScript forms the basis for the logic that our instrument - an hour meter - uses:

class HourMeter extends BaseInstrument {
    constructor() {
        super();
        this.decimals = 1;
    }
    get templateID() { return "HourMeter"; }
    connectedCallback() {
        super.connectedCallback();
        this.digits = [];
        this.digitsBot = [];
        for (let i = 1; i <= 6; i++) {
            this.digits.push(this.getChildById("d" + i));
            this.digitsBot.push(this.getChildById("d" + i + "Bot"));
        }
    }
    disconnectedCallback() {
        super.disconnectedCallback();
    }
    parseXMLConfig() {
        super.parseXMLConfig();
        if (this.instrumentXmlConfig) {
            let mode = this.instrumentXmlConfig.getElementsByTagName("Decimals");
            if (mode.length > 0) {
                this.decimals = parseInt(mode[0].textContent.toLowerCase());
            }
        }
        for (let i = this.digits.length - 1; i >= this.digits.length - this.decimals; i--) {
            diffAndSetAttribute(this.digits[i], "state", "decimal");
            diffAndSetAttribute(this.digitsBot[i], "state", "decimal");
        }
    }
    Update() {
        super.Update();
        let hour = SimVar.GetSimVarValue("GENERAL ENG ELAPSED TIME:1", "hour");
        for (let i = this.digits.length - 1; i >= 0; i--) {
            if (hour < 0) {
                hour = 0;
            }
            let power = this.digits.length - i - 1 - this.decimals;
            let digit = Math.floor((hour % Math.pow(10, power + 1)) / Math.pow(10, power));
            if (this.digits[i].textContent != digit + '') {
                diffAndSetText(this.digits[i], digit + '');
                diffAndSetText(this.digitsBot[i], ((digit + 1) % 10) + '');
            }
            if (Math.pow(10, power) * (digit + 1) < (hour % Math.pow(10, power + 1)) + 0.001) {
                diffAndSetStyle(this.digits[i], StyleProperty.transform, "translate(0vh,-" + ((100000 * hour) % 100) + '' + "vh)");
                diffAndSetStyle(this.digitsBot[i], StyleProperty.transform, "translate(0vh,-" + ((100000 * hour) % 100) + '' + "vh)");
            }
            else {
                diffAndSetStyle(this.digits[i], StyleProperty.transform, "");
                diffAndSetStyle(this.digitsBot[i], StyleProperty.transform, "");
            }
            hour -= 0.0001;
        }
    }
}
registerInstrument("hour-meter-element", HourMeter);

This example encapsulates all the essential methods that will be required for most instruments you create:

  • First it extends the BaseInstruments class
  • Next it gets the template ID (which matches the ID set in the HTML file, which is shown below)
  • We then add the callback method which will be called when the custom element is removed from the DOM
  • After that we check for XML from the panel.xml file
  • Finally it sets up the update script which controls what the JS will do each update frame

Once that's done, at the very end of the JS file, you need to register instrument using the registerInstrument() method. This is vitally important as this method is what registers the instrument class to BaseInstrument and it will insert the associated HTML inside of an hour-meter-element custom tag element.

 

The HTML

The JavaScript given above needs to be accompanied by an HTML file, which looks like this:

<link rel="stylesheet" href="HourMeter.css" />
<script type="text/html" id="HourMeter">
    <div id="Mainframe">
        <div id="d1">0</div>
        <div id="d1Bot" class="bot">1</div>
        <div id="d2">0</div>
        <div id="d2Bot" class="bot">1</div>
        <div id="d3">0</div>
        <div id="d3Bot" class="bot">1</div>
        <div id="d4">0</div>
        <div id="d4Bot" class="bot">1</div>
        <div id="d5">0</div>
        <div id="d5Bot" class="bot">1</div>
        <div id="d6">0</div>
        <div id="d6Bot" class="bot">1</div>
    </div>
</script>
<script type="text/html" import-script="/Pages/VCockpit/Instruments/Generic/Misc/HourMeter/HourMeter.js"></script>    

We won't go into exactly what's going on here as the basis for the HTML is explained further up this page (HTML Setup), although it's worth emphasising that the very last thing in the file should be the <script> call that references the JavaScript file for the instrument logic.

 

The CSS

Finally, here's the CSS that is required for the styling of the hour-meter instrument:

:root {
  --bodyHeightScale: 1;
}
@keyframes TemporaryShow {
  0%, 100% {
    visibility: visible;
  }
}
@keyframes TemporaryHide {
  0%, 100% {
    visibility: hidden;
  }
}
html {
  height: 100%;
  width: 100%;
  overflow: hidden;
}
html body {
  -webkit-user-select: none;
  font-family: var(--font);
  font-size: calc(var(--viewportHeightRatio) * (36px / 21.6) * var(--currentPageHeight) / 100 );
  color: white;
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
}
#highlight {
  position: absolute;
  height: 100%;
  width: 100%;
  z-index: 10;
  pointer-events: none;
}
#Electricity {
  width: 100%;
  height: 100%;
}
#Electricity[state=off] {
  display: none;
}
hour-meter-element {
  background-color: #121212;
  height: 100vh;
  width: 100vw;
  display: inline-block;
  overflow: hidden;
}
hour-meter-element #Mainframe {
  position: absolute;
  height: 100vh;
  width: 100vw;
  background-color: #121212;
  overflow: visible;
  font-size: 75vh;
  line-height: 100vh;
  font-weight: bold;
}
hour-meter-element #Mainframe div {
  background-color: #121212;
  color: #a0a0a0;
  position: absolute;
  height: 100vh;
  width: 16.6666666667vw;
  vertical-align: central;
  text-align: center;
  border-left: 0.5vw solid black;
  border-right: 0.5vw solid black;
}
hour-meter-element #Mainframe .bot {
  bottom: -100vh;
}
hour-meter-element #Mainframe #d1, hour-meter-element #Mainframe #d1Bot {
  left: 0vw;
}
hour-meter-element #Mainframe #d1[state=decimal], hour-meter-element #Mainframe #d1Bot[state=decimal] {
  background-color: #a0a0a0;
  color: black;
}
hour-meter-element #Mainframe #d2, hour-meter-element #Mainframe #d2Bot {
  left: 16.6666666667vw;
}
hour-meter-element #Mainframe #d2[state=decimal], hour-meter-element #Mainframe #d2Bot[state=decimal] {
  background-color: #a0a0a0;
  color: black;
}
hour-meter-element #Mainframe #d3, hour-meter-element #Mainframe #d3Bot {
  left: 33.3333333333vw;
}
hour-meter-element #Mainframe #d3[state=decimal], hour-meter-element #Mainframe #d3Bot[state=decimal] {
  background-color: #a0a0a0;
  color: black;
}
hour-meter-element #Mainframe #d4, hour-meter-element #Mainframe #d4Bot {
  left: 50vw;
}
hour-meter-element #Mainframe #d4[state=decimal], hour-meter-element #Mainframe #d4Bot[state=decimal] {
  background-color: #a0a0a0;
  color: black;
}
hour-meter-element #Mainframe #d5, hour-meter-element #Mainframe #d5Bot {
  left: 66.6666666667vw;
}
hour-meter-element #Mainframe #d5[state=decimal], hour-meter-element #Mainframe #d5Bot[state=decimal] {
  background-color: #a0a0a0;
  color: black;
}
hour-meter-element #Mainframe #d6, hour-meter-element #Mainframe #d6Bot {
  left: 83.3333333333vw;
}
hour-meter-element #Mainframe #d6[state=decimal], hour-meter-element #Mainframe #d6Bot[state=decimal] {
  background-color: #a0a0a0;
  color: black;
}

Again, we won't go into any details about this file, since it follows standard CSS practices and formats.

 

The Panel.xml

For completeness, the example instrument also makes use of the panel.xml file, which is an optional file used to give certain additional functionality to one or more instruments on an individual aircraft basis. In this case the file would look like this for our hour meter instrument:

<PlaneHTMLConfig>
    <Instrument>
        <Name>HourMeter</Name>
        <Decimals>2</Decimals>
        <Electric>
            <Simvar name="CIRCUIT GENERAL PANEL ON" unit="Boolean"/>
        </Electric>
    </Instrument>
</PlaneHTMLConfig>

For this instrument the XML is very simple and is used only to set the number of decimal places to show, and also tell the simulation which electrical circuit controls the power to it.