Zum Inhalt

Nutzer JavaScript Code

Um eine einfache Möglichkeit zu bieten, AvNav an seine Bedürfnisse anzupassen, kann man mit ein wenig Java Script Code AvNav relativ einfach erweitern. Die möglichen Erweiterungen werden in den folgenden Kapiteln beschrieben. Prinzipiell kann man beliebigen Java Script Code ausführen - muss dabei aber natürlich zusehen, die Funktionen von AvNav nicht zu stören.

Die in diesem Dokument beschriebenen Funktionen stehen sowohl dem Nutzer-JavaScript-Code zur Verfügung als auch dem JavaScript-Code in Plugins.

Der Java Script Code liegt bei den Nutzerdateien in der Datei user.mjs. Diese Datei ist ein JavaScript Modul und wird von AvNav am Ende seines Start-Prozesses geladen. Der Hauptinhalt dieser Datei ist eine exportierte Funktion, die von AvNav nach dem Laden aufgerufen wird. Der Parameter "api" ist das AvNav interface das durch den Code genutzt werden kann.

Ein minimaler Inhalt der Datei könnte so aussehen:

export default (api)=>{
    api.log("Hello world);
}

Aufräumen

Damit ein Neu-Laden des JavaScript Codes funktioniert (besonders wichtig auch für Plugins) muss darauf geachtet werden, das alle globalen Resourcen wieder freigegeben werden, wenn das Modul nicht mehr benötigt wird. Was bedeutet das? Globale Resourcen sind z.B. timer die direkt am Windows-Objekt gestartet worden sind. Falls diese nicht anders bereits wieder gestoppt werden, sollte eine "Aufräum-Funktion" geschrieben werden, die sie stoppt.

Eine solche Aufräum-Funktion kann als Resultat der Default-Export-Funktion zurückgegeben werden.

Beispiel:

export default (api)=>{
    //start a timer (global resource)
    const timer=window.setInterval(()=>console.log("timer"),1000);
    //a cleanup function to stop the timer
    //or free other global resources
    const cleanup=()=>{
        if (timer) window.clearInterval(timer);
    }
    //return the cleanup function
    return cleanup;
}
Wenn der Code keine solchen globalen Resourcen nutzt, muss auch keine Cleanup-Funktion zurückgegeben werden. Die Aufrufe am AvNav-Api sind keine globalen Resourcen - dort erfolgt das Aufräumen intern in AvNav.
Kompatibilität zu früheren Versionen - user.js

In früheren Versionen befand sich der JavaScript code in einer Datei user.js. Das war eine einfache JavaScript Datei (kein Modul). Das neue Handling mit user.mjs hat einige Vorteile:

  • automatisches Neu-Laden bei Änderungen
  • erweitertes API (mehr Funktione möglich)
  • es können weitere Module geladen werden.

Falls AvNav durch ein Update installiert wurde und eine alte user.js Datei existiert, wird diese weiter genutzt. Das zur Verfügung stehende API entält allerdings nur einen Teil der Funktionen des aktuellen APIs. Da diese Date in der aktuellen Version ebenfalls über ein JavaScript Modul geladen wird und Module strikteren JavaScript code verlangen, kann es zu Fehlern beim Laden der Datei kommen. Dann muss man seinen Code ggf. anpassen. Die alte Beschreibung für die user.js ist hier verfügbar. Falls sowohl eine user.mjs (neu) als auch eine user.js (alt) vorhanden sind, wird nur die user.mjs geladen.

Bearbeiten der Datei user.mjs

Über

 ->->

erreicht man die Liste der Dateien im Nutzerverzeichnis. Nach Klick auf die Datei "user.mjs" und wird ein Editorfenster geöffnet, in dem man den Code bearbeiten kann. Um effektiv arbeiten zu können, empfiehlt es sich, mit zwei Browser-Fenstern oder Tabs zu arbeiten - in einem Fenster hat man den Editor geöffnet, im anderen Fenster AvNav noch einmal in dem Bereich, den man testen möchte. Beim Speichern der Datei im Editor-Fenster wird diese im Hintergrund sofort in allen offenen AvNav Fenstern neu geladen und wird direkt wirksam.

Der Editor hat eine Syntax-Anzeige für JavaScript, so das man bereits bis zu einem gewissen Grad die Korrektheit des Codes sehen kann.

Für die Arbeit an der Datei sollte man in dem Browser-Fenster, in dem man testet, die Entwickler-Werzeuge öffnen und dort die "Konsole" sichtbar haben. Fehler im JavaScript Code werden dort ausgegeben.

Wenn man die Bearbeitung beendet hat, sollte man AvNav in allen Browser-Fenstern neu laden (einen Hinweis darauf bekommt man durch das rote Icon in der Titelzeile - ein Klick darauf führt zu einem Dialog für das Neu-Laden).

In der vom System angelegten user.mjs Datei befinden sich bereits einige Beispiele, die man durch Entfernen von Kommentarzeichen aktivieren kann.

Das aktuelle Template kann man auch auf github finden.

Formatierer (Formatter)

Ein typischer Anwendungsfall für einen neuen Formatter ist die Anzeige eines bisher nicht bekannten Wertes über das Default Widget (siehe Layouts). Das könnte z.B. ein über einen XDR Datensatz empfangener Lade- oder Entladestrom sein. In den Daten ist der Wert in A, wir möchten den Wert aber gerne in % eines Maximalwertes anzeigen lassen. Dazu kannb man den folgenden Code nutzen:

const formatAmperePercent=(val)=>{
  const max=50; //fix max
  if (isNaN(val)) return "-----";
  const percent=Number(val)*100/50;
  return percent.toFixed(0)+"%";
}
api.registerFormatter("formatAmperePercent",formatAmperePercent);
Wenn man diesen Formatter so in die user.mjs geschrieben hat, kann man nun sofort im Layout Editor den Formatter nutzen indem man das Default Widget auswählt, unter "value" den anzuzeigenden Wert selektiert und unter "formatter" den eingefügten "formatAmperePercent" auswählt.

Natürlich ist das so noch nicht besonders komfortabel - der Maximalwert ist fest kodiert. Wenn man möchte, das der Maximalwert im Layout Editor auswählbar wird, muss man noch Parameter für den Formatter festlegen. Man definiert nur einen Parameter "maxCurrent".

const formatAmperePercent=(val,max)=>{
  if (isNaN(val)) return "-----";
  if (! max) max=50;
  const percent=Number(val)*100/max;
  return percent.toFixed(0)+"%";
}
formatAmperePercent.parameters=[
  {
    name: "maxCurrent",
    type: "FLOAT",
    default: 50,
    description: "Maximal current for computing the percentage"
  }
]
api.registerFormatter("formatAmperePercent",formatAmperePercent);
Die Parameter für einen Formatter werden ähnlich beschrieben, wie Widget Parameter, nur das sie als Array angegeben werden mit einem zusätzlichen Feld "name" Wenn man num im Layout-Editor den neuen Formatter nutzt, sieht das Bild so aus:

Formatter

Formatter mit Paremetern

Und das Resultat dann (unteres Widget):

Formatter Result

Formatter Resultat

Falls man mit der einfachen JavaScript "toFixed" Funktion nicht zufrieden ist, kann man natürlich stattdessen auch einen der bereits vorhandenen Formatter verwenden. Wenn man z.B. 3 Stellen haben möchte, kann man "formatDecimal" verwenden.

...
return api.formatter.formatDecimal(percent,3)+"%";

Anzeigen (Widgets)

Man kann die folgenden Arten von Anzeigen hinzufügen:

  • Widgets mit eigenem Formatter (und ggf. festen Werten) basierend auf dem Default Widget (Beispiel 1 - user.mjs: rpmWidget)
  • Anpassung/Erweiterung der Grafik Widgets mit canvas gauges (Beispiel 2 - user.mjs rpmGauge)
    Hiermit ist es auch möglich, Parameter zugänglich zu machen, die in den bisher vorhandenen Widgets nicht enthalten sind
  • Widgets mit eigenem HTML code (Beispiel 3 - user.mjs: userSpecialRpm)
  • Widgets mit Grafik in einem Canvas Element (Beispiel im TestPlugin: testPlugin_courseWidget)
  • Widgets mit eigenem HTML, die mit dem Server Teil eines Plugins interagieren (TestPlugin: testPlugin_serverWidget)
  • Widgets, die Grafiken auf der Karte darstellen (type: map) z.B. SailInstrument

Das Interface, über das mit AvNav kommuniziert wird, findet sich auf github. Für map Widgets kann über das Api auf die zugrunde liegenden Bibliotheken für geografische Berechnungen zugegriffen werden (Funktion LatLon und Dms).

Canvas Gauges

Für Canvas Gauge Widgets können bestimmte Parameter (siehe Canvas Gauges Beschreibung) entweder auf feste Werte gesetzt werden (dann müssen sie in der Widget Definition vorhanden sein - siehe die Werte im Beispiel) oder sie können für den Nutzer im Layout Editor einstellbar gemacht werden (dann müssen sie als WidgetParameter gesetzt werden.

Ausserdem kann eventuell ein eigener Formatter definiert werden und als default für das Widget gesetzt werden.

Wenn man für bestimmte vordefinierte Parameter vermeiden möchte, das sie im Layout Editor für den Nutzer sichtbar werden, müssen sie in den editable Parameters mit false angegeben werden.

var rpmGaugeUserParameter = {
...
formatter: false,
formatterParameters: false
};

Für jedes gauge widget muss der Parameter "type" angegeben werden - entweder "radialGauge" oder "linearGauge".
Ausserdem haben sie den zusätzlichen Parameter

drawValue (boolean)

Dieser Parameter steuert, ob auch eine numerische Anzeige des Wertes erfolgen soll. Der originale "valueBox" Parameter der canvas gauges wird ignoriert!

Neben den Parametern kann auch noch eine translateFunction definiert werden. Diese erhält als Parameter ein Objekt mit den aktuellen Werten aller Parameter und kann dieses modifizieren, bevor sie an canvas gauges übergeben wird. Diese Funktion muss "zustandsfrei" sein, d.h. die Werte der Rückgabe dürfen nur von den übergebenen Werten (und anderen unveränderlichen Werten) abhängig sein. Andernfalls werden potentiell Änderungen nicht in der Anzeige sichtbar.

Eigene Widgets

Für ein selbst geschriebenes Widget können die folgenden Funktionen/Eigenschaften implementiert werden:

Name Typ Nutzbar bei Typ Beschreibung
name String alle der Name des Widgets
type String (optional) alle Bestimmt, welches Widget erzeugt werden soll. Werte: radialGauge, linearGauge, map Wenn der Typ nicht gesetzt ist, wird entweder das default widget genutzt (keine Funktion renderHtml und keine Funktion renderCanvas angegeben) - oder ein nutzer definiertes Widget (userWidget)
renderHtml Funktion (optional) userWidget Diese Methode muss HTML zurückgeben, das dann in das Widget eingebaut wird. Das als Parameter an renderHtml übergebene Objekt enthält die unter storeKeys und parameters definierten Werte. Die Funktion wird jedesmal erneut aufgerufen, wenn sich die Werte geändert haben. Die "this" variable innerhalb von renderHtml zeigt auf ein Objekt, das spezifisch für das Widget ist (Kontext).
renderCanvas Funktion (optional) userWidget, map Mit dieser Funktion kann in das übergebene Canvas Objekt gezeichnet werden. Das als zweiter Parameter an renderCanvas übergebene Objekt enthält die unter storeKeys und parameters definierten Werte. Die Funktion wird jedesmal erneut aufgerufen, wenn sich die Werte geändert haben. Die "this" variable innerhalb von renderCanvas zeigt auf ein Objekt, das spezifisch für das Widget ist (Kontext). Für map widgets ist dieser Canvas ein Overlay, das über die Karte gelegt wird. Am Widget Kontext stehen Funktionen zur Umrechnung von Koordinaten in Canvas Pixel bereit. Es ist wichtig den Canvas korrekt mit save/restore zu beschreiben, da sich alle map widgets den gleichen Canvas teilen.
storeKeys Object alle Hier müssen die Daten angegeben werden, die aus dem zentralen Speicher gelesen und als Parameter den renderXXX Funktionen mitgegeben werden sollen. Siehe Store
caption String (optional) alle Eine default Beschriftung
unit String (optional) alle Eine default Einheit
formatter Funktion (optional) defaultWidget, radialGauge, linearGauge Ein Formatierer für den Wert. Für das defaultWidget muss diese Funktion angegeben werden.
translateFunction Funktion (optional) alle ausser map Diese Funktion wird mit den aktuellen Werten als Parameter aufgerufen (so wie bei storeKeys angegeben) und muss die daraus berechneten Werte zurückgeben. Falls keine eigene renderXXX Funktion genutzt werden soll, kann hier vor dem Rendern eine Umrechnung von Werten erfolgen
initFunction Funktion (optional) userWidget, map Falls vorhanden, wird diese Funktion einmalig aufgerufen, wenn das Widget erzeugt wird. Als Parameter (und als this) ist der Widget Context vorhanden. Dieses Objekt hat eine eventHandler Eigenschaft - hier müssen die im renderHTML genutzten eventHandler eingetragen werden. Mit der Funktion triggerRedraw am Widget Kontext kann ein erneuter Aufruf der renderXXX Funktionen erzwungen werden. Der 2. Parameter enthält die Eigenschaften des Widgets. Das sind insbesondere auch alle editierbaren Widget Parameter, die definiert wurden.
finalizeFunktion Funktion (optional) userWidget, map Falls vorhanden, wird diese Funktion aufgerufen, bevor das Widget nicht mehr genutzt wird. Die "this" Variable zeigt wieder auf den Widget Kontext. Ausserdem ist der Kontext auch als erster Parameter vorhanden - wie bei der initFunction.

Nach der Definition muss das Widget bei AvNav bekannt gemacht werden (api.registerWidget).

Widget Context

User Widgets und Map Widgets bekommen einen WidgetContext. Dieser wird für jedes Widget erzeugt und den Funktionen:

  • initFunction (this und erster Parameter)
  • finalizeFunction (this und erster Parameter)
  • renderHtml (this)
  • renderCanvas (this)

übergeben.
Damit der Kontext als this Parameter genutzt werden kann, müssen die Funktionen "klassisch" mittels function definiert werden und nicht als "arrow function".

Richtig:

let userWidget={  
 renderHtml: function(context,props){  
 return "<p>Hello</p>";  
 }  
}

Im WidgetContext können Nutzerdaten gespeichert werden, die in aufeinanderfolgenden Aufrufen benötigt werden.
Ausserdem enthält er einige Funktionen, die vom Widget Code aufgerufen werden können.

Name Widget Parameter Beschreibung
eventHandler userWidget --- eventHandler ist keine Funktion sondern ein array. Falls im renderHtml event Handler angegeben werden dann muss in der initFunction  eine Funktion mit diesem Namen hier registriert werden.
triggerRedraw userWidget --- Diese Funktion muss gerufen werden, wenn das Widget (z.B. nach einer Kommunikation mit dem Server) möchte, das es neu gezeichnet wird.
lonLatToPixel map lon,lat Konvertiert die Koordinaten in pixel Koordinaten für das Zeichnen in renderCanvas. Gibt ein array mit x,y Koordinate zurück.
pixelToLonLat map x,y Berechnet aus den Canvas-Koordinaten x,y longitude und latitude. Gibt ein array mit lon,lat zurück.
getScale map --- Gibt den Scaling Faktor für das Display zurück. Hochauflösende Display haben einen scaling Factor > 1. Gezeichnete Objekte (besonders Text) sollten in ihren Dimensionen angepasst werden.
getRotation map --- Gibt die Drehung der Karte (in radian!) zurück
getContext map --- Gibt den renderingContext2D des Canvas zurück (nur aktiv innerhalb der renderCanvas Funktion)
getDimensions map --- gibt die Größe des Canvas zurück [Breite,Höhe]
triggerRender map --- gleiche Funktion wie triggerRedraw beim user Widget

Widget Parameter

Neben der Widget Definition können beim registrieren des Widgets noch Parameter angegeben werden, die dann im Layout Editor für das Widget angezeigt werden.

Beispiele sind im user.mjs Template zu finden. Die Werte, die im Layout Editor für diese Parameter angegeben werden, stehen später in den renderHtml und renderCanvas Funktionen zur Verfügung (Ausnahme: Typ KEY, hier wird der  aus dem Speicher gelesene Wert zur Verfügung gestellt).

Für jeden Parameter kann man die folgenden Werte angeben:

Name Type Beschreibung
type String STRING, NUMBER,FLOAT, KEY, SELECT, ARRAY, BOOLEAN, COLOR Der Typ für den Parameter. Je nach Typ wird er dem Nutzer unterschiedlich angezeigt. Für COLOR eine Farb-Auswahl, für SELECT eine AuswahlListe und für KEY die Liste der momentan verfügbaren Werte im Store. Für ein Array kann eine durch Komma getrennte Liste angegeben werden.
default je nach type Der default Wert. Für COLOR eine color css Property - also z.B. "rgba(200, 50, 50, .75)"
list Array (nur für type SELECT) Ein Array von Strings oder von Objekten {name:'xxx',value:'yyy'} - diese Werte werden  zur Auswahl angezeigt. Statt eines Arrays kann auch eine Funktion angegeben werden, die ein Array zurückgibt - oder eine Funktion, die eine Promise zurückgibt, deren resolve Funktion dann das Array liefert.
description string Eine Beschreibung des Wertes für den Nutzer
condition Object oder Array von Objecten Ein Object mit Werten für andere Parameter. Nur wenn die anderen Parameter den entsprechenden Wert haben, wird der Parameter angezeigt. Wenn ein Array angegeben ist, werden die einzelnen Element oder-verknüpft. Beispiel condition:{kind:'all'}. Der Parameter wird nur angezeigt, wenn der Parameter "kind" den Wert "all" hat.

Es gibt eine Reihe von vordefinierten Parametern für den Layout Editor. Bei diesen wird zur Beschreibung kein Objekt mit Eigenschaften angegeben, sonder nur true oder false (das zeigt, ob sie zum Ändern angeboten werden sollen oder nicht).

Das sind:

  • caption (STRING)
  • unit (STRING)
  • formatter (SELECT)
  • formatterParameters (ARRAY)
  • value (KEY)
  • className (STRING)

Ein Beispiel für eine Definition:

var exampleUserParameters = {
//formatterParameters is already well known to avnav, so no need for any definition
//just tell avnav that the user should be able to set this
formatterParameters: true,
//we would like to get a value from the internal data store
//if we name it "value" avnav already knows how to ask the user about it
value: true,
//we allow the user to define a minValue and a maxValue
minValue: {type: 'NUMBER', default: 0},
maxValue: {type: 'NUMBER', default: 4000},
};

Bibliotheken und Bilder

Falls der eigene Java Script code auf Bibliotheken oder Bilder zugreifen soll, können diese in das gleiche Verzeichnis hochgeladen werden - Images auch in das Images Verzeichnis.

Bibliotheken, die sich im Nutzerdaten-Verzeichnis befinden, können einfach mit

import './libtest.js';
geladen werden. Falls die Bibliotheken ihre Funktionen als Module anbieten, findet man in der Beschreibung, welche imports genutzt werden können.
import test from './libtest.js';
Für einen default export.

Es empfiehlt sich, für alle Widgets css Klassen zu vergeben, damit man diese dann mit nutzerspezifischem CSS anpassen kann. IDs sollten nicht verwendet werden, da die Elemente potentiell mehrfach auf der Seite auftauchen können.

Falls Daten vom Server geladen werden sollen, empfiehlt sich die Verwendung von fetch. Alle Dateien im user Verzeichnis (oder im plugin Verzeichnis für plugin.js)  sind nach dem Schema api.getBaseUrl()+"/"+name abrufbar.

Feature Formatierer(featureFormatter)

Es gibt die Möglichkeit, eigene Funktionen zu registrieren, die die Anzeige von Daten aus Overlays aufbereiten.
Solche Funktionen können in der user.mjs oder in Plugins implementiert werden.

Mit

api.registerFeatureFormatter('myHtmlInfo',myHtmlInfoFunction);

werden sie registriert. Diese Java Script Funktion bekommt als Parameter die in der Overlay Datei vorhandenen Eigenschaften des angeklickten Punktes und kann veränderte/neue Eigenschaften zurückgeben, die dann vom FeatureInfo Dialog angezeigt werden.

Die folgenden Eigenschaften können zurückgegeben werden:

Name Bedeutung
sym die URL für ein anzuzeigendes Icon. Das kann eine relative URL sein, diese ist dann eine Icon Datei innerhalb der konfigurierten userIcons Datei, ein absoluter Pfad wie z.B. /user/images/myImage.png oder eine mit http: beginnende externe URL (natürlich dann nur mit Internet Verbindung nutzbar).
name der anzuzeigende Name
desc der unter "description" anzuzeigende Text
htmlInfo ein html String, der dann bei Klick auf den Button angezeigt wird.
time eine Zeitangabe (String oder java script Date)
link eine URL, die bei Klick auf den Button angezeigt werden soll (alternativ zu htmlInfo). Es gelten die gleichen Regeln wie für "sym".

Die übergebenen Parameter hängen von der Overlay Datei ab. Zusätzlich sind in jedem Falle die Werte "lat" und "lon" vorhanden.

Ein Beispiel für die eingebaute genericHtmlInfo Funktion, die alle vorhandenen Werte als HTML in den Wert htmlInfo schreibt.

let genericHtmlInfo=function(properties,extended){
if (! extended) return {};
let htmlInfo='<div class="featureInfoHtml">';
for (let k in properties){
if (!properties[k]) continue;
if (htmlInfo !== "") htmlInfo+="<br/>";
htmlInfo+=avnav.api.escapeHtml(k)+"="+avnav.api.escapeHtml(properties[k]);
}
htmlInfo+='</div>';
return {htmlInfo:htmlInfo};
}

Der zweite Parameter, der an die Funktion übergeben wird, gibt einen Hinweis, ob alle Parameter erzeugt werden sollen oder nur der "sym" Parameter. Falls extended auf "false" gesetzt ist, sollte die Funktion keine zeitraubenden Operationen ausführen, da sie potentiell für jedes Element aus dem Overlay aufgerufen wird.

Nachdem eine solche Funktion registriert wurde, kann sie für ein Overlay in der Konfiguration ausgewählt werden.

Kartenlayer (User Map Layer)

Über die Funktion

api.registerUserMapLayer(baseName,name,callback)
können eigene Karten-Layer-Typen registriert werden. Eine Beschreibung findet man unter Kartendetails.

HTML Code

An verschiedenen Stellen (bei Widgets, Dialogen) kann HTML code an das API übergeben werden. Dafür gibt es die folgenden Möglichkeiten:

Text

Das ist die auch bereits in allen älteren AvNav Versionen verfügbare Variante. Hier wird einfach der HTML code als Text übergeben. <div class="test">Test</div>. In JavaScript kann man dazu recht gut Template Strings verwenden.

const v=99:
return `<div class="value">${v}</div>`
Falls man interaktive Elemente einbauen möchte, geht das nur bei eigenen Widgets. EventHandler für Elemente müssen vorher registriert werden (siehe initFunction) und werden im HTML code einfach mit <button onclick="myHandler">Click!</button> angegeben (das ist keine exakte HTML Syntax, da nur der Name des event handlers angegeben wird, kein java script code). Wenn der EventHandler aufgerufen wird, zeigt this auf den Widget-Kontext.

ReactJs

AvNav selbst ist unter Nutzung von ReactJS geschrieben. Um dem Nutzer erweiterte Möglichkeiten beim Einbringen von eigenem Code zu bieten, kann HTML auch als ReactNode zurückgegeben werden. Da für den Nutzer-JavaScript Code kein JSX Parser bereitsteht, und das Erzeugen der React-Objekte in purem JavaScript Code mühsam ist, bietet AvNav hier Support für HTM. Um das zu nutzen muss man die entsprechenden Module am Anfang der user.mjs Datei importieren.

import html from '/modules/htm.js';
Ein simples Widget, das eine wiederverwendbare Componente Value zur Anzeige nutzt, kann dann so aussehen:
//a simple React component that will render a div with the class "value"
//and display the parameter "v" inside
const Value=({v})=>html`<div class="value">${v}<//>`

//a simple widget that will render the value of the selected
//store key using the Value component
const HTMWidget={
  name:'HTMWidget',
  renderHtml:(props)=>{
    return html`<${Value} v=${props.value}><//>`
  }
}
const HTMParameters={
  value:{
    type:"KEY",
  }
}
api.registerWidget(HTMWidget,HTMParameters);
Unter Nutzung von React Funktionen ist es auch sehr viel einfacher z.B. einen Klick-Handler einzubauen.

const Value=({v})=>{
   const clickHandler=(ev)=>alert("Value:"+v);
   return html`<div class="value" onClick=${clickHandler}>${v}<//>`
}
Es stehen alle ReactJS Funktionen zur Verfügung. Die nötigen Module lassen sich über entsprechende Imports hinzufügen. Ausserdem stehen auch einige AvNav GUI Elemente über Imports zur Verfügung. Die bereitgestellten Module finden sich im Code. Wenn man z.B. ListItem von den AvNav Modulen importieren möchte, schreibt man im code
import {ListItem} from '/modules/avnavui.js`;

Einige Beispiele zur Nutzung kann man in einem AvNav eigenen Plugin zur Netzwerk-Verwaltung sehen.

AvNav Navigationsdaten (Store)

Um Anzeigen dynamisch an geänderte Werte anzupassen, verfolgt AvNav ein "Store" Konzept. In diesem Store werden Daten mit einem strukturierten Key und ihrem Wert gespeichert. Ein solcher Key ist z.B. nav.gps.lat für die aktuelle Länge der GPS Position. Die innerhalb von AvNav verwendeten Keys finden sich in keys.ts.

Der AvNav store kann auf verschiedene Arten genutzt werden:

  1. Für dynamische Anzeigen

    An einigen Stellen (z.B. bei Widgets oder Buttons) kann man einen Paremeter storeKeys angeben. Dieser wählt einige der Werte im Store aus, die auf Änderungen überwacht werden sollen. Immer wenn sich diese ändern wird das entsprechende Element (also z.B. das Widget neu gezeichnet). Gleichzeitig übersetzen die Einträge die strukturierten Keys in einfache.

    const storeKeys={
       lat:'nav.gps.lat',
       lon: 'nav.gps.lon'
    }
    
    const testWidget={
       name: 'testWidget',
       renderHtml: (props)=>return `<span>lat=${props.lat}, lon=${props.lon}</span>`,
       storeKeys: storeKeys
    }
    

    Die Werte nav.gps.lat und nav.gps.lon werden überwacht und an das Widget werden die Properties lat und lon übergeben. Die Elemente, die storeKeys akzeptieren, haben ausserdem einen Parameter updateFunction. Dieser erhält als Parameter die aktuell gelesenen Werte und kann veränderte Werte zurückgeben.

    const testWidget={
       ...
       updateFunction=(current)=> return {...current, valid: current.lat != null && current.lon != null}
    }
    

    Dieses Beispiel gibt noch einen zusätzlichen Wert valid zurück, wenn lat und lon gültige Werte haben. Mit diesem Konzept lassen sich relativ flexibel die vorhandenen Elemente dynamisch nutzen.

  2. Direkter Zugriff

    Die API-Funktionen

    getStoreBaseKey():string;
    getStoreData(key:string,defaultv?:StoreData):StoreData;
    setStoreData(key:string,data:StoreData):void;
    
    ermöglichen den Zugriff auf den Store. Der lesende Zugriff auf die Store-Daten ist dabei für beliebige Keys möglich, der schreibende Zugriff nur für Keys die mit dem Prefix beginnen, der von getStoreBaseKey() zurückgegeben wird. Damit wird sichergestellt das Plugins und Nutzer-Code nur ihre eigenen Daten in den Store schreiben können und nicht die Grundfunktionen von AvNav stören.

    Prinzipiell können beliebige Daten in den Store geschrieben werden, man muss jedoch aufpassen, wenn man z.B. Objekte in den Store schreibt und diese ändern möchte. Bei Änderungen muss man ein solches Objekt vorher kopieren und dann die Kopie in den Store schreiben, da AvNav sonst nicht erkennen kann, das sich der Wert geändert hat.

    Ein Anwendungsfall für einen eigenen Wert im Store kann beispielsweise die Steuerung der Sichtbarkeit oder des "disabled" Zustandes eines User-Buttons sein.

    Natürlich kann es auch für Anzeigen in einem Widget genutzt werden - beispielsweise für Werte, die per fetch von einem Server geladen werden.

    const SKEY=api.getStoreBaseKey()+".TestButton";
    //initially hide the button
    api.setStoreData(SKEY,false);
    api.registerUserButton({
       name: 'test',
       shortText: 'Test',
       storeKeys: {
           visible: SKEY
       }
       ...
    });
    //call this to show/hide the button
    const showOrHideButton=(show)=>{
       api.setStoreData(SKEY,show)
    }
    
    3. In React HTML-Code

    Hier steht eine Helper-Funktion bereit, die die AvNav Store Werte mit dem React State verknüpft, so das bei Änderungen auch die Komponente neu gezeichnet wird.

    import {useStoreState} from '/imports/avnavui.js';
    ....
    const StoreValue=({vkey})=>{
       const [current]=useStoreState(vkey,'not set');
       return html`<div className="value">${current}</div>`
    }
    
    ....
    const SKEY=api.getStoreBaseKey()+".TestValue";
    api.setStoreData(SKEY,1)
    const TestComp=()=>{
       return html`${StoreValue} vkey=${SKEY} <//>`
    }
    
    window.setInterval(()=>{
       api.setStoreData(SKEY,api.getStoreData(SKEY)+1);
    },1000)
    
    Dieses Beispiel zeigt den Wert aus dem Store mit der Komponente StoreValue bei jeder Änderung an.

Dialoge und Buttons

AvNav bietet die Möglichkeit HTML Seiten oder externe Webseiten als User Apps einzubinden. Daneben gibt es auch noch die Option Buttons auf bestimmte Seiten zu plazieren und mit diesen Buttons Aktionen auszulösen - z.B. Dialoge zu starten oder auch Aktionen in einem Plugin.

Das API bietet dazu einige Funktionen.

Funktion Beschreibung
registerUserButton Registriere ein Button mit einer Callback-Funktion, die bei Click aufgerufen wird.
showDialog Zeige einen Dialog an.

Buttons

Ein Button, der mit registerUserButton bekannt gemacht wird, hat die folgenden Eigenschaften (Details findet man im API):

Name Typ Beschreibung
name String Name des Buttons. Der Name wird auch als CSS Klasse am Button gesetzt und kann dazu genutzt werden, im CSS das Icon oder die Texte anzupassen
iconClass String Optional. Siehe API
shortText String Optional. Wenn nicht gesetzt sollte der Text per CSS gesetzt werden.
longText String Optional. Text für den Tool-Tip oder für den Button wenn er im Hauptmenü sichtbar ist. Alternativ per CSS.
icon URL Optional. Relative Icon-URL. Alternativ per CSS.
storeKeys Objekt Optional. Siehe Store.
updateFunction Funktion Optional. Siehe Store.
visible Boolean Optional. Wenn false wird der Button nicht angezeigt.
disabled Boolean Optional. Wenn true wird der Button disabled gesetzt.
toggle Boolean Optional. Wenn true wird der Button auf aktiv gesetzt. (Grüner Rand)
onClick Funktion Die Funktion, die bei Klick aufgerufen wird.

Der page Parameter in registerUserButton ist die Seite, auf der der Button angezeigt werden soll. Die Liste der Seitennamen findet man im Code - oder wenn man im Dialog für das Anlegen einer User App die "page" auswählt.

Die onClick Funktion erhält als Parameter den ReactJS Klick-Event.

Dialoge

Eine der Funktionen, die man durch einen Button auslösen kann, ist die Anzeige eines Dialogs.

AvNav bietet dazu die API Funktion showDialog. Der übergebene Parameter vom Typ DialogConfig beschreibt den Dialog, der angezeigt werden soll. Die Funktion arbeitet asynchron (d.h. gibt eine Promise zurück). Der Resolve Wert (bzw. der async Returnwert) ist eine Funktion, mit der man den Dialog wieder schliessen kann. Für viele Szenarien wird man das nicht benötigen. Der Dialog wird in jedem Fall geschlossen, wenn die Seite in AvNav verlassen wird.

Die Parameter des DialogConfig Objektes sind im API Code beschrieben.

Parameter die als Typ string|React.ReactNode haben, können mit einem String oder wie unter ReactJS beschrieben gefüllt werden. Falls der Dialog vor allem dazu dienen soll ein Formular mit Werte-Eingaben anzuzeigen, kann im Feld parameters ein Array von Parameter-Definitionen übergeben werden. Die Syntax entspricht den Widget Parametern. Initiale Werte werden in values gesetzt (ein Objekt dessen Keys die Parameter-Namen sind). Falls der Nutzer einen Wert ändert, wird der onChange Callback gerufen.

Über den buttons Parameter wird eine Liste von anzuzeigenden Buttons angegeben. Die Elemente entsprechen weitgehend der Beschreibung unter Buttons. Die onClick Funktion bekommt als zusätzliche Parameter die aktuellen Werte und eine Funktion zum Schliessen des Dialogs übergeben. Wenn sie (optional async - d.h. als Promise) ein Objekt zurückgibt, wird dieses als neue Parameterwerte interpretiert. Falls der Parameter close an einem Button nicht gesetzt (oder true) ist, wird der Dialog geschlossen.

const INITIALVALUES={
    testvalue:'to be changed'
}
let ivalues={...INITALVALUES}
api.showDialog({
    title: 'Test',
    parameters:[
        name: 'testvalue',
        type: 'STRING'
    ],
    buttons: [
        {
            name: 'reset',
            shortText: 'Reset',
            //do not close the dialog when clicked
            close: false,
            //return an object with the new values to be set
            onClick:(ev,values)=> return INITIALVALUES
        },
        {
            name:'cancel',
            shortText:'Cancel',
        }
        {
            name:'ok'
            shortText:'Ok',
            onClick:(ev,values)=>{
                alert("testvalue="+values.testvalue);
            }
        },
    ]
})