• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

MSFS20 Gauge development workflow

Messages
13
Country
unitedstates
Hey folks - I've had some pretty good success working on gauges but one question I had was about the workflow. Is there any way to reload the community folder without having to back entirely out of the game? Having to completely restart every time I want to test a change is pretty painful so any tips would be greatly appreciated.
 
Ok, I think I have this down pretty well now. I am having a bit of an issue. My main file looks like:

HTML:
<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8" />

    <title>Test SVG</title>

    <script src="/JS/coherent.js"></script>
    <script src="/JS/common.js"></script>

    <style>
        body {
            background-color: red;
            color:black;
            font-size:80px
        }
    </style>

</head>

<body>

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras mauris odio, semper sed lectus eu, consequat tincidunt ante. Suspendisse ornare dictum luctus. Nunc sodales purus sed ipsum varius, quis feugiat libero sollicitudin. Phasellus tincidunt malesuada condimentum. Donec gravida volutpat velit ac congue. Sed dignissim dui diam, cursus pretium lorem efficitur at. Aliquam at vulputate dolor. Morbi finibus elit ut dolor hendrerit, a finibus massa dignissim. Vivamus dignissim quis ipsum vel tincidunt. Phasellus sapien libero, faucibus congue faucibus quis, condimentum sed sapien. Nunc tellus tellus, dictum sit amet venenatis sit amet, ultricies at est. Suspendisse elementum vel nulla ac viverra. Cras scelerisque nunc nec tincidunt rutrum.

</body>

</html>

While I can get the screen to turn red I cannot, for the life of me, get anything to appear from the body. What might I be doing wrong?
 
Not really sure, but you may want to close the font-size attribute with a semicolon as a try.
(general html styling, do not have MSFS)
font-size:80px;
 
Thanks @spokes2112 but, unfortunately, that's not it. I've also tried

Code:
<script type="text/html" id="AS3X_Touch">body stuff... </script>

This syntax doesn't really work either.
 
I'm on maybe my 20th HTML/JS gauge, and still don't have the comprehensive answers, but have working gauges and a reasonable workflow. To explain my workflow I've included the files.

HTML, CSS, JS code of an example gauge below which paints to a 256x256 $flap_display texture, in an "AS-33" aircraft.

The gauge files are in html_ui/Pages/VCockpit/Instruments/AS-33/flap_indicator/

The gauge uses two images "background_ok.png" and "background_nok.png" both of which are simply alternate plain LCD backgrounds 256x256, one normal, one pinkish.

The example only writes to the $texture - most of my HTML/JS gauges are also updating local variables ("L:..." vars) which are used in the model/AS-33_interior.xml to animate needles etc.

In terms of workflow (your actual question) the trick is to update the 'querystring' letters after the references to the .js and .css files in the .html any time you change either of those, and in the 'Dev' Aircraft Selector window click load. I.e. see flap_indicator.html below, the relevant lines (with redundant '?br' and '?dp' added to the url's) are:

Code:
<link rel="stylesheet" href="flap_indicator.css?br" />
and
Code:
<script type="text/html" import-script="/Pages/VCockpit/Instruments/AS-33/flap_indicator/flap_indicator.js?dp"></script>

Given I start with "?aa" on the end of those url's, you can see I do a lot of updates & reloads...

This method works because adding the harmless ?xx to the end of the JS/CSS url will cause MSFS to think you're referencing a new file & won't re-use the current one from its 'cache'.

As a couple of extra notes on this gauge example:

The HTML contains multiple 'divs' layered on top of each other using absolute positioning.

For the gauge you do not define the entire page complete with <html> tags - MSFS controls that. See my .html file below.

You update the content of a div using standard JS, i.e. document.getElementById("mydiv").innerHTML = "HELLO"; - this is how the example writes e.g. the FLAP INDEX as a text number into the div.

You hide/show divs using standard JS, i.e. document.getElementById("mydiv").style.display='none' (or 'block') - that's the way this example flips the background images.

There is a fairly complicated linkage between the references in the HTML/JS/CSS files and in most gauge examples the same reference is used for multiple different components. In my example below the JavaScript class is called flap_indicator_class, the MSFS-created special HTML element is called flap_indicator-element and the tag within your .html file that will embed your html inside that element is called flap_indicator_script so you can see where these things link to each other in the example - that's impossible to see when all those things are just called flap_indicator, so having different id's is better for an example (but ultimately not necessary).

The tiniest mis-step in the JS will cause the gauge to NOT DISPLAY AT ALL, and debugging then is difficult (there are tools that can help, but it's still difficult). so the MOST IMPORTANT ELEMENT OF THE WORKFLOW is to make changes in small steps so when the gauge blows up (it will...) you have pretty good idea of where the issue occurred. For future reference, putting a 'try..catch' around the updates called from within the "Update()" function, and writing an error code to the "#debug" div will help a lot, but I didn't want to make the example more complex.

The example below doesn't include the ability to 'click' on the gauge, i.e. as if it's a touch screen, but again that's standard JS document.getElementById('mydiv').onclick = (your function) but also you must have a class method "get isInteractive() { return true; } for clicking to be permitted.

panel.cfg:

Code:
VCockpit08]
size_mm=256,256
pixel_size=256,256
texture=$flap_display
emissive            = 0

htmlgauge00=AS-33/flap_indicator/flap_indicator.html,0,0,256,256

flap_indicator.html

Code:
<link rel="stylesheet" href="flap_indicator.css?br" />

<script type="text/html" id="flap_indicator_script">
    <div id="background_ok"></div>
    <div id="background_nok"></div>
    <div id="display"></div>
    <div id="debug"></div>
</script>

<script type="text/html" import-script="/Pages/VCockpit/Instruments/AS-33/flap_indicator/flap_indicator.js?dp"></script>

flap_indicator.css

Code:
: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: Roboto-Regular;
    font-size: calc(var(--viewportHeightRatio) * (36px / 21.6) * var(--currentPageHeight) / 100);
    color: white;
    height: 100%;
    width: 100%;
    margin: 0;
    padding: 0; }

flap_indicator-element {
  background-color: #121212;
  height: 100vh;
  width: 100vw;
  display: inline-block;
  overflow: hidden; }

/* Normal LCD background */
#background_ok {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-image: url("/Pages/VCockpit/Instruments/AS-33/flap_indicator/background_ok.png");
    background-repeat: no-repeat;
    /* background-size: cover; */
}

/* Warning LCD background */
#background_nok {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    /* display: none; */
    background-image: url("/Pages/VCockpit/Instruments/AS-33/flap_indicator/background_nok.png");
    background-repeat: no-repeat;
    /* background-size: cover; */
}

#display {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: transparent;
    font-size: 200px;
    text-align: center;
    /* vertical-align: middle; */
    color: black;
    display: flex;
    justify-content: center;
    align-content: center;
    flex-direction: column;
}

flap_indicator-element #debug {
    position: absolute;
    top: 5%;
    left: 30%;
    width: 30%;
    height: 10%;
    font-size: 45px;
    color: black;
}

flap_indicator.js

Code:
/*
  flap_indicator
*/

class flap_indicator_class extends BaseInstrument {

    constructor() {
        super();
        this._isConnected = false;
    }

    get templateID() { return "flap_indicator_script"; } // ID of <script> tag in flap_indicator.html

    // ********************************************************************
    // ********** CONNECTED CALLBACK         ******************************
    // ********************************************************************
    connectedCallback() {
        super.connectedCallback();

        this._isConnected = true;
    }

    // ********************************************************************
    // ********** DISCONNECTED CALLBACK         ***************************
    // ********************************************************************
    disconnectedCallback() {
        super.disconnectedCallback();
    }

    // ********************************************************************
    // ********** GAUGE UPDATE CALLED ON SIM UPDATE CYCLE      ************
    // ********************************************************************
    Update() {
        // We read the sim variables into local vars for efficiency if multiple use.
        this.ALTITUDE_M = SimVar.GetSimVarValue("A:INDICATED ALTITUDE", "meters");

        this.flap_index = SimVar.GetSimVarValue("A:FLAPS HANDLE INDEX","number");
        this.spoilers_out = SimVar.GetSimVarValue("A:SPOILERS HANDLE POSITION","percent") > 0;
        this.gear_down = SimVar.GetSimVarValue("A:GEAR HANDLE POSITION", "bool") ? true : false;
        //DEBUG BALLAST NOT IMPLEMENTED
        this.carrying_ballast = false; //get(DATAREF_BALLAST_KG) > 20;
        this.time_now_s = SimVar.GetSimVarValue("E:ABSOLUTE TIME","seconds");
        this.alt_agl_ft = SimVar.GetSimVarValue("PLANE ALT ABOVE GROUND", "feet");

        this.update_flap_indicator();
    }

    // ********************************************************************
    // ********** GAUGE UPDATE CALLED ON SIM UPDATE CYCLE      ************
    // ********************************************************************

    // Runs once on startup
    flap_indicator_init() {
        if (this.flap_indicator_init_complete == null) {

            this.WHEEL_DOWN_ALT_LIMIT_FT = 2000; // warning if gear down above 2000 feet AGL

            this.MAX_WARNING_DURATION_S = 5; // only leave warning on indicator for max time
            this.warning_time_s = 0.0; // record time of issuing warning
            this.warning_gear_up = false;
            this.warning_gear_down = false;
            this.warning_ballast = false;

            this.background_nok_el = this.getChildById("background_nok");
            this.display_el = this.getChildById("display");
            this.display_el.innerHTML = "AB";
            this.flap_indicator_init_complete = true; // prevent further runs
        }
    }

    update_flap_indicator() {
        this.flap_indicator_init();
        // SPOILERS / GEAR UP WARNING
        if (this.spoilers_out && ! this.gear_down) {
            if (! this.warning_gear_up) {
                this.background_nok_el.style.display = 'block';
                this.display_el.style.fontSize = '50px';
                this.display_el.innerHTML = 'SPOILERS<br/>GEAR UP';
                this.warning_time_s = this.time_now_s;
            }
            this.this.warning_gear_up = true;
        } else {
            this.warning_gear_up = false;
        }

        // AT HEIGHT / GEAR DOWN WARNING
        if (this.gear_down && (this.alt_agl_ft > this.WHEEL_DOWN_ALT_LIMIT_FT)) {
            if (! this.warning_gear_down) {
                this.background_nok_el.style.display = 'block';
                this.display_el.style.fontSize = '70px';
                this.display_el.innerHTML = 'GEAR<br/>DOWN';
                this.warning_time_s = this.time_now_s;
            }
            this.warning_gear_down = true;
        } else {
            this.warning_gear_down = false;
        }

        let on_ground = this.alt_agl_ft < 10;

        // SPOILERS / BALLAST WARNING
        if (! on_ground && (this.spoilers_out || this.gear_down) && this.carrying_ballast) {
            if (! this.warning_ballast) {
                this.background_nok_el.style.display = 'block';
                this.display_el['font-size'] = '40px';
                this.display_el.innerHTML = 'SPOILERS<br/>BALLAST';
                this.warning_time_s = this.time_now_s;
            }
            this.warning_ballast = true;
        } else {
            this.warning_ballast = false;
        }

        let warning_expired = this.time_now_s > (this.warning_time_s + this.MAX_WARNING_DURATION_S);

        let no_warning = ! this.warning_gear_down && ! this.warning_gear_up && ! this.warning_ballast;

        if (no_warning || warning_expired) {
            // All seems fine, so set flap_indicator to flaprqst
            let display_text = '-';
            let font_size = '200px';
            switch (this.flap_index) {
                case 0:
                case 1:
                case 2:
                    display_text = ''+(this.flap_index+1);
                    break;
                case 3:
                    display_text = '4A';
                    font_size = '180px';
                    break;
                case 4:
                    display_text = 'T1';
                    font_size = '180px';
                    break;
                case 5:
                    display_text = 'T2';
                    font_size = '180px';
                    break;
                case 6:
                    display_text = 'Land';
                    font_size = '100px';
                    break;
                default:
                    break;
            }
            this.background_nok_el.style.display = 'none';
            this.display_el.style.fontSize = font_size;
            this.display_el.innerHTML = display_text;
        }
    }
}

registerInstrument("flap_indicator-element", flap_indicator_class);
 
@B21-soaring - thank you sooo soo much for such a thoughtful reply. This obviously took a second to put together so thank you very very much. Glad to hear that many of the issues I'm running into are not just me being inept. :)

I will definitely look this through with more detail when I can get back into my project at home. If I have any follow up questions I'll be sure to post them here. Again, thanks so much.
 
Not necessary for your first gauge, but here's what the Update() function can look like if you have exception trapping. Note each function call within Update is preceded by a new value for 'x' and a div is added to the gauge HTML specifically to display error messages:

Code:
    // ********************************************************************
    // ********** GAUGE UPDATE CALLED ON SIM UPDATE CYCLE      ************
    // ********************************************************************
    Update() {
        this.lx_adi_init();

        // We read the sim variables into local vars for efficiency if multiple use.
        this.TIME_S = SimVar.GetSimVarValue("E:ABSOLUTE TIME", "seconds");
        this.AIRSPEED_MS = SimVar.GetSimVarValue("A:AIRSPEED INDICATED", "meters per second");
        this.ALTITUDE_M = SimVar.GetSimVarValue("A:INDICATED ALTITUDE", "meters");
        this.SPOILERS_HANDLE_POSITION = SimVar.GetSimVarValue("A:SPOILERS HANDLE POSITION", "percent");

        let x=0;
        try {
            x=1;this.update_needle();
            x=2;this.update_top_number();
            x=3;this.update_altitude();
            x=4;this.update_spoilers();
        } catch (e) {
            document.getElementById("lx_adi_debug").innerHTML = "Ex."+x;
        }
    }

The advantage of this is when your update code dies in an unexpected way (it will), the gauge doesn't just collapse to black - the updates stop with e.g. Ex.3 highlighted in the dial which gives you a major clue where the problem is in your JS code. Obviously you can refine this basic method as much as you like.

Note the lx_adi_debug div defined last in the .html file, it is normally empty but will be displayed above all the others:
Code:
<link rel="stylesheet" href="lx_adi.css?cj" />

<script type="text/html" id="lx_adi_script">
    <div id="lx_adi">
        <div id="lx_adi_numbers">
            <div id="lx_adi_10000s"></div>
            <div id="lx_adi_1000s"></div>
            <div id="lx_adi_100s"></div>
            <div id="lx_adi_10s"></div>
        </div>
        <div id="lx_adi_front_plate"></div>
        <div id="lx_adi_alt_feet"></div>
        <div id="lx_adi_airspeed_knots"></div>
        <div id="lx_adi_top_number"></div>
        <div id="lx_adi_pressure">1013</div>
        <div id="lx_adi_spoilers_green"></div>
        <div id="lx_adi_spoilers_red"></div>
        <div id="lx_adi_debug"></div>
    </div>
</script>

<script type="text/html" import-script="/Pages/VCockpit/Instruments/AS-33/lx_adi/lx_adi.js?ct"></script>

And the lx_adi_debug CSS is defined:
Code:
/* ***************************************** */
/* lx_adi_debug                                          */
/* ***************************************** */
#lx_adi_debug {
    position: absolute;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    color: red;
    font-size: 32px;
    background: transparent;
}


At some point someone will mention https://github.com/dga711/msfs-webui-devkit - this can be useful but with a large number of small complex gauges (for gliders) I finds the 'dedicated debug div' approach more suitable for my work.
 
Last edited:
Yeah, I did get the msfs-webui-devkit up and running and it was of some help but I did have a few issues with it getting to open up at times without a touch screen interface. That said, the refresh is pretty invaluable.
 
Hey folks - I've had some pretty good success working on gauges but one question I had was about the workflow. Is there any way to reload the community folder without having to back entirely out of the game? Having to completely restart every time I want to test a change is pretty painful so any tips would be greatly appreciated.
Hi Dolbex,

I am interested about your workflow because I was asking myself the same question, can you tell me more about it?
When you update the gauge code, what do you do to have it updated in the sim without restarting it all?

Thanks,
Eric
 
@eric_marciano - really I just used msfs-webui-devkit to make everything happen. Things got pretty wild and I had some fun messing with what was possible. I got tailwindcss and Vue in there successfully and while things aren't perfect it was pretty cool to see.

1616605408424.png
 
msfs-webeui-devkit seems useful if you're developing a large flat-panel display (because it uses the gauge display area to show its debug info) but it seems less useful if you're developing a complex HTML/JS gauge that actually only has small UI elements. E.g. a HTML/JS soaring variometer may move a needle and display a couple of numbers in the background (like estimated arrival height at the next waypoint) - then the "gauge display = the debug info area" becomes quite a limitation.

Contrary to some views, I have the HTML/JS gauge support as a very important capability within MSFS replacing a lot of code that was previously (FSX) in RPN/XML and C++.

So in MSFS you can choose between:

* RPN/XML in the <model>.xml file - great for relatively simple <Code> that computes an animation or updates gauge variable used elsewhere. Here is where you can easily specify custom events (the new H: events) triggered by mouse events on model objects that get passed smoothly to your HTML/JS gauges.

* HTML/JS - JS is a full-featured language similar to C/C++, so you can write very substantial complex gauges reading SimVars and user interaction events, particularly (due to the HTML/CSS support) those with a digital UI (e.g. modern flat-panel instruments). TBH I have it as the best language even to program needle movements where those aren't a trivial one-liner from some built in MSFS SimVar (this happens in soaring instruments a lot). The downside (or upside) compared to raw C++ executables is the HTML/JS gauges are *not* running as native Windows executables with full access to the user's PC.

* C++ compiled to WASM and run with some of the same system access restrictions as HTML/JS (but additionally includes the SimConnect API)

* A native windows C++ application running in a separate process with full access to the desktop/network and communicating with MSFS via the SimConnect API.
 
Yeah, one of the things I could not figure out was how to develop HTML/JS gauges OUTSIDE of the simulator (and maybe you can't). For me the best workflow would be to develop 90% of it outside the sim and then fire it up when you're doing final testing and such but I never got that far - not for a lack of trying :)
 
Yeah, one of the things I could not figure out was how to develop HTML/JS gauges OUTSIDE of the simulator (and maybe you can't). For me the best workflow would be to develop 90% of it outside the sim and then fire it up when you're doing final testing and such but I never got that far - not for a lack of trying :)
yeah I totally agree - HTML/JS is inherently close to supporting that dev approach but there's a fundamental API disconnect - there are two main ways a HTML/JS gauge interacts with the aircraft: SimVar.GetSimVarValue and SimVar.SetSimVarValue (Asobo missed the memo calling it "get" and "set") and the ability to call a JS callback on custom events - it would need a general purpose 'bridging' gauge planted in the aircraft that could redirect events and SimVar requests to/from a 'shim' "SimVar" module used in the browser version. It's inherently doable so I'm optimistic some worthy person will get around to doing it - at the moment many of us are flat out developing gauges and the pause required to improve the workflow in that way hasn't been available.
 
yeah I totally agree - HTML/JS is inherently close to supporting that dev approach but there's a fundamental API disconnect - there are two main ways a HTML/JS gauge interacts with the aircraft: SimVar.GetSimVarValue and SimVar.SetSimVarValue (Asobo missed the memo calling it "get" and "set") and the ability to call a JS callback on custom events - it would need a general purpose 'bridging' gauge planted in the aircraft that could redirect events and SimVar requests to/from a 'shim' "SimVar" module used in the browser version. It's inherently doable so I'm optimistic some worthy person will get around to doing it - at the moment many of us are flat out developing gauges and the pause required to improve the workflow in that way hasn't been available.
That's a great idea - however, I'd hate to develop something with their updates right around the corner (seems like the next couple months have a lot of SDK work coming out).
 
Finally found a discussion about what struck me as the most left out topic for anyone diggin' in to MSFS panels and gauges. The workflow. How things are connected. Reading the existing documentation (both official and the most obvious search hits) gives details about various stuff. None connects those details together very well nor do they explain how to work with them.

It's quite easy to understand that there's SimConnect to interact with MSFS (or FSUIPC if you prefere), and that there's WASM C++ gauges if you want that. Some mention of HTML panels and packaging. And some repos here and there doing things just a little different from everyone else. It's also quite easy to get something to work. Whatever approach you choose. At least if you're somewhat used to development and programming. But since documentation is sparse and heavily neglected by MS, the journey from a working sample to something more, seems quite long. This is the first time I see someone actually trying to summarize the various options and try to answer when to use what and why.. And some options on how to debug things. Another good source of information I found was this video
which I believe was linked in another thread here.

In the end: Just wanted to give a shoutout and thanks to everyone contributing to this thread, really. It might just be me that's a slow learner och suck at finding info. IDK. ;)

/Jens

ps.
Question: what do you guys dev the HTML/JS parts in? And what JS frameworks? Noticed Vue here, which is nice since I've played with that in another project.
I just got notified about the FBW ACE project which use React.

(If I ever were to contribute something here, I'd love to make some tutorials or overviews to fill in those gaps I feel is making this harder than it has to be. Not there yet though, still trying to figure things out myself first.)
ds.
 
A big development for HTML/JS since this thread started is the CoherentGT Debugger...

That gives you an 'inspect' window similar to the one in Chrome or Firefox to look at the html/css you're manipulating and (woop woop) your console.log() statements write to the embedded console.

Another plus is you no longer have to tweak the URL's of your JS and CSS files (by adding ?<random> after the URL) in the gauges HTML to get an 'Aircraft Select' to reload fresh code.

I do NOT edit my HTML/JS gauges within the "MSFS Project" dev framework - the /html_ui/ tree is essentially independent of all that anyway. Instead I simply work on the working 'packaged' aircraft (I'm editting the /html_ui/ content, plus panel.cfg, plus model/model.xml and that's what get's shipped with the finished product).

Crucially (given the approach above), I always have copy of the MSFS Layout Generator at the root of my package (i.e. in the same folder as layout.json) so I can drag-and-drop layout.json onto it any time I want to update the VFS. This works like a charm. However, an aircraft 'reload' or 'resync' (see below) are not enough to re-sync the virtual file system in-game, so more reliably I will quit the game and reload if I've created new files - for the bulk of my gauge development this isn't happening very often, i.e. my soaring gauges tend to take a long time to program but the graphics / animations tend to stabilize relatively early on in the development & after that I might improve the graphics but as often as not that doesn't involve additional files.

I use the freeware Atom editor for my HTML/JS/CSS/JSON work (I've standardized on that editor for Windows and Linux so choosing it was a broad dev decision, not just MSFS). There are many 'plugins' for the editor and I use pretty-json a LOT to look at the model/*.gltf file to confirm the animations etc that I'm programming are in there as expected - this is particularly important when you initially create the 'stub' gauge (i.e. before you've done much of the programming) as a variety of mistakes at that stage can mean the gauge simply doesn't load or work AT ALL and you need to check whether the glitch is in the panel.cfg, the HTML/JS, the model.xml or the model.gltf.

As suggested above I use the MSFS / Developer Toolbar / Window / Aircraft Selector menu to hit 'LOAD' to reload the gauges after each edit (I have done this literally thousands of times).

When there's a more substantive change, e.g. a change also involving the model.xml, then I hard reload the whole aircraft with MSFS / Developer Toolbar / Tools / Project Editor / Create New (always the same project name "foo") / Cancel the next 'type of project' window / Close the Project Editor. At this point the MSFS / Developer Toolbar / Tools menu is fully active to include "Aircraft Editor"

I must have written 40+ HTML/JS gauges by now and TBH the starting point is generally to cut-and-paste a known working simple gauge into a new folder (like an ASI) and global-replace every reference in the HTML/JS/CSS files from "asi" to "computer_vario" or whatever. That gives a reasonably reliable start to a working gauge that appears in the cockpit (still looking suspiciously like a second ASI, maybe without a needle) which I can do surgery on and days or weeks later it will have evolved into a FLARM or something. Also TBH the gauge NEVER starts up first time, there's always some mistake in the HTML, JS, CSS, panel.cfg which adds up to a big fat zero but I'm experienced enough now to look back through the code and easily work out which small change I missed. Once the gauge is up-and-running (albeit mostly un-implemented) it is easy to backtrack any time a bad change blows the gauge up.

A positive development in MSFS is all my 'steam' gauges (ASI, Altimeter, Varios, etc) have their background textures rendered by the HTML/JS (rather than solely a hard-coded model UV map) and all these gauges are (counter-intuitively) specified within the JS code to be 'interactive', i.e. mouse clicked on the background texture will get picked up in a callback in the gauge. So I can systematically allow a click on a gauge background to change the units Imperial/Metric, and use the MSFS persistent key-value storage API to remember this setting for subsequent flights. I've found on most gauges this behaviour can co-exist with whatever normal cool functionality is expected from the gauge, so it's all upside which the pilot can ignore if they wish. Also after working with many combinations of gauges, I've found it preferable to SHARE these units 'preference' settings between gauges, i.e. if you click the Altimeter to switch from feet to meters, the 'arrival height' calculation in the nav instrument will show meters also - that might not be true of everyone's gauges but modern soaring instruments often tend to be displaying height, speed, distance or whatever so on balance it's better they concur than needing to change each individually. Note that changing the units/background often requires a new formula for the needle movements (the backgrounds aren't always simply aligning).

Finally (for this post), there is quite a lot of 'template' code you can accumulate which is common to most of your HTML/JS gauges. This includes handling electrical power on/off (and you may have gauges go through a power-on sequence), placing your SimVar.GetSimVar... calls at the top of the update loop so you don't end up with multiple calls reading the same SimVar, storing/retrieving settings between flights, deciding how to pass model xml 'click' events through to your html/js (there are multiple ways), how to consistently handle JS exceptions (i.e. bugs...), structuring your JS code so each main section can have an 'init' method called on startup as well as an 'update' method.

Regarding JS frameworks, I've kept my JS code intentionally lightweight knowing pieces of the code are going to get called EVERY sim update cycle. A simple illustration is I avoid pretty much any loop occuring anywhere in my JS code. There are examples when I need to loop but I know where those are (e.g. in an interpolation function, and in the code that loads and processes the MSFS flightplan) and I can be confident the loops are very short. Nothing wrong with many frameworks but I haven't needed one yet - I'm not a huge fan of Angular / Angular2 on the interwebs anyway so maybe I'm just biased. Perhaps if I write gauge code that opens websockets to some shared server somewhere then I'll suck it up and adopt a framework for the more complex architecture.
 
A big development for HTML/JS since this thread started is the CoherentGT Debugger...
So THAT's where it is! See, I've come to the insight that MSFS use CoherentGT and that there's a debugger but I couldn't find it. Was looking in every direction except within the MSFS SDK itself, lol!


I must have written 40+ HTML/JS gauges by now and TBH the starting point is generally to cut-and-paste a known working simple gauge into a new folder
Starting to realize that's how people do. It bothers me though, being a dev myself I tend to wanna organize workflows to be effective. Or, preferably, get the workflow ready made. :)


Regarding JS frameworks, I've kept my JS code intentionally lightweight
That's my thoughts too. It's tempting to go all in with some framework or tooling just to take shortcuts. This is, in my opinion, the wrong reason to do so. It's better to choose to use one if you feel that there's a need for it. Ie it provides functionality you lack with current setup. Then you can argue for opting in on a particular framework or tool. If it solves the problem you want to solve that is. Better to go from ground up - if you can. Less things can go wrong and you learn the basics on the way.

Speaking of which... One thing I don't understand is the use of different classes. In some examples, the class used is
JavaScript:
TemplateElement
and in others
JavaScript:
BaseInstrument
. The different classes are aimed for different scenarios I get that - and looking into the libs of MSFS it's possible to deduce what they do. Feels like there should be easier ways to select when to use what etc.

From what I gather, there's two approaches when it comes to making HTML panels. Or at least two. One is to override a certain default instrument or panel. In this case, I understand that the structure has to be identical in order for the override to work. Makes sense. One such example would be this: http://kronzky.info/fs/fs2020-landgauge/index.html

Another scenario is to make something custom, like a custom panel to be used in VR (that's my goal atm). If I've not gotten it all backwards, structure is less important as long as the layout.json is correct - and that packages are built properly. But I havent toyed with different variants enough to know this. Just tried some samples and fiddled a little with them. Perhaps there are other scenarios as well that affects what class(es) or templates you mught wanna use. And yet, I've only covered HTML gauges here. Then there's standalone or WASM gauges...

Still, thank you so much @B21-soaring for your explanations and pointers. Much appreciated!
 
Back
Top