Native JS

Mit Native JS führen Sie JavaScript-Code innerhalb eines Ninox-Skripts aus und teilen Funktionen und Variablen mit ihm. So kann Native JS Ninox-Variablen lesen und die in derselben Formel deklarierten Funktionen aufrufen; die Funktionen des globalen Codes dagegen werden über ninext.callNinoxFunction aufgerufen (siehe unten, „Eine Funktion des globalen Codes aufrufen“).

Native JS ist standardmäßig aktiv; es lässt sich in den Ninext Einstellungen ausschalten, Abschnitt „JavaScript-Integration“ (Natives JavaScript).

Bild.png

Einschränkungen

Ändert man eine Ninox-Variable in Native JS, so wirkt sich das nicht auf die Ninox-Variable aus.

JavaScript wird ausschließlich lokal ausgeführt. JavaScript in Code einzufügen, der auf dem Server läuft, hat keine Wirkung. Ebenso hat es keine Wirkung, aus servergestütztem Code eine globale Funktion aufzurufen, die JavaScript enthält. Ab Version 3.2.0 meldet der Reiter Fehler einen Block, der direkt in einem Tabellen-Trigger (nach dem Anlegen, nach dem Ändern) oder in einem do as server steht.

Welche Codes lokal und welche auf dem Server ausgeführt werden, sehen Sie hier:

Bild.png

Syntax

Ausführung

Um Ninox mitzuteilen, dass Sie zu JavaScript wechseln, müssen Sie den Code in #{ ... }# einschließen:

Ninox
#{ alert("hello world") }#;

Denken Sie daran: Ninox ist eine vorkompilierte Sprache, die den Typ jeder Variablen und jeder Funktion im Voraus kennen muss. Dasselbe gilt für Native JS – Sie müssen den vom JavaScript-Code zurückgegebenen Typ angeben. Standardmäßig gibt Native JS Text zurück; Sie können aber den gewünschten Rückgabetyp angeben, indem Sie ihn ganz am Anfang des Codes mit der Schreibweise :[type] notieren:

Die Annotation schließt direkt an #{ an, ohne jedes Leerzeichen: #{:number …, nicht #{ :number …. Ebenso folgen zwei Annotationen ohne Leerzeichen aufeinander (:text:callback). Mit einem Leerzeichen wird die Annotation nicht erkannt: Der Block ist vom Typ Text, und die Annotation bleibt im JavaScript-Code stehen, wo sie einen Syntaxfehler verursacht.

Rückgabewert

Ninox
#{:text return "hello world"}#;

Die möglichen Typen sind: text, number, date, timestamp (Datum und Uhrzeit), time, timeinterval, appointment, boolean, html, color, icon, email, phone, location, file, user, json und any. Die vollständige Liste, mit choice, link, style und styled, schlägt die Konsole vor (siehe unten).

Achtung

Ein Datum mit Uhrzeit wird mit :timestamp annotiert, während ein Funktionsparameter desselben Typs als datetime deklariert wird. :datetime gibt es als Annotation nicht: Der Block wäre vom Typ Text. Ab Version 3.2.0 meldet der Reiter „Fehler“ eine unbekannte Annotation.

Datums-, Uhrzeit- und Dauerwerte werden in Millisekunden ausgetauscht: Eine Ninox-Variable dieses Typs kommt im JavaScript als Zahl an, und ein Block :date oder :array(date) muss Millisekunden zurückgeben (das, was getTime() eines JavaScript-Datums liefert).

Array-Typen

Um ein Array von Werten zurückzugeben, geben Sie mit :array(type) den Typ seiner Elemente an (ab Version 3.2.0):

Ninox
#{:array(number) return [1, 2, 3] }#;

Alle einfachen Typen sind möglich: :array(text), :array(date), :array(any) …

Bis Version 3.1.2 kann nur ein Array vom Typ any zurückgegeben werden. Diese Schreibweise bleibt gültig, als Entsprechung von :array(any):

Ninox
#{:any(array) return [1, 2, 3] }#;

Die Schreibweise :number(array), die diese Seite früher zeigte, gibt kein Array zurück. Seit Version 3.2.0 wird sie im Reiter „Fehler“ gemeldet.

Datensatztypen

Um Datensätze zurückzugeben, verwenden Sie nid oder rid, gefolgt vom Tabellennamen in Klammern:

Verwenden Sie nid, müssen Sie ein Array von Textwerten mit dem vollständigen Datensatzbezeichner zurückgeben, also Tabellenbezeichner + Datensatznummer:

Ninox
#{:nid(customer) return ['A1','A2','A3'] }#

Verwenden Sie rid, geben Sie die Datensatznummer zurück:

Ninox
#{:rid(customer) return [1,2,3] }#

Bei zusammengesetzten Tabellennamen setzen Sie kein ' wie in Ninox, sondern schreiben einfach den vollständigen Namen der Tabelle:

Ninox
#{:rid(my long name table) return [1,2,3] }#

Mit dieser Syntax liefert Native JS Variablen, die sich genauso verhalten wie Ninox-Code. Sie können also folgende Schreibweise verwenden, als handelte es sich um ein select:

Ninox
(#{:rid(customer) return [1,2,3] }# order by 'Last Name').'First Name';

Bild.png

Eingabehilfe in der Konsole

Ab Version 3.2.0 hilft die Konsole beim Schreiben eines Blocks:

  • in der Annotation: Wenn Sie direkt nach #{ ein : tippen, schlägt sie die Rückgabetypen, array(type), nid(Table) und rid(Table) vor; wenn Sie nach nid oder rid eine ( tippen, die Liste der Tabellen, und nach array die der Typen; ein zweites : schlägt die Modifikatoren callback und async vor;
  • im JavaScript: Die Autovervollständigung schlägt die Ninox-Variablen und -Funktionen vor, die der Block erhält, in ihrer JavaScript-Form, und nach einem Punkt die Elemente des Objekts – bei einem Datensatz die Namen seiner Felder (siehe „JavaScript-Autovervollständigung in nativen Blöcken“ auf der Seite Konsole).

Diese Hilfe gibt es nur in der Konsole.

Was Ninext prüft

Ab Version 3.2.0 (Beta). Der Reiter Fehler prüft die Blöcke #{ … }# der gesamten Datenbank, die Konsole die des Codes, den Sie dort schreiben.

Gemeldet werden:

  • ein JavaScript-Syntaxfehler;
  • eine unbekannte Annotation (wie :datetime) oder eine nicht gefundene Tabelle in :nid(…) oder :rid(…): Der Block wäre vom Typ Text;
  • ein typisierter Block ohne jedes return oder mit Pfaden, die keines erreichen: Er würde einen leeren Wert liefern;
  • ein Block :callback oder :async, der callback nie aufruft: Die Formel würde nie einen Wert erhalten;
  • ein Block, der direkt in einem Tabellen-Trigger (nach dem Anlegen, nach dem Ändern) oder in einem do as server steht, wo er nicht ausgeführt wird.

Datensatz ändern

Variablen vom Typ Datensatz werden im JSON-Format übergeben, mit einem Schlüssel-Wert-Paar für jedes Feld des Datensatzes.

Ninox
var r := first(select Customer);
#{:text return r['First Name'] }#;

Der Inhalt der Variablen r sieht in JavaScript so aus:

Ninox
{
 "Created on": 1648910817206,
 "Id": "A1",
 "Last modified": 1714817525840,
 "Modified by": "7zBqhAKXxfQAPmH7Y",
 "First Name": "Sofia",
 "Last Name": "Young",
 "Company": "Kollwitz GmbH",
 "Postal Code": "12345",
 "City": "New Jersey",
 "Address": "11,South Commerce Street",
 "Email": "sofi@kollwitz-gmbh.com",
 "Customer No": "K0001",
 "Title": "2",
 "Invoices": [
  1,
  16,
  17
 ],
 "Photo": "shutterstock_307218608.jpg",
 "Category": "1",
 "Position": "Marketing head",
 "Checked": true,
 "ID": "A1",
 "id": "A1"
}

Das Objekt trägt außerdem einen Schlüssel _node: den Datensatz, wie Ninox ihn speichert, seine Felder über ihre internen Kennungen bezeichnet.

Wichtig

Je nachdem, ob der Code in der Mac-Anwendung oder im Web ausgeführt wird, kann die ID unterschiedlich groß- und kleingeschrieben sein.
Um dem zu begegnen und Konflikte zwischen den Plattformen zu vermeiden, wird die ID in allen möglichen Schreibweisen wiederholt.

Ändert man den Datensatz in JavaScript, so wird er in Ninox nicht aktualisiert.

Asynchrone Ausführung

Manchmal ist es sinnvoll, die Ausführung von Ninox-Code mit JavaScript zu synchronisieren.
Verwendet man zum Beispiel einen Timer, so ist es wichtig, dass der Code erst dann endet, wenn der Timer abgelaufen ist.
Ohne diese Synchronisierung liefe der Code nach der JavaScript-Ausführung sofort weiter, was zu unerwartetem Verhalten führen kann.

Im ersten Fall wird der Wert „I don’t know“ sofort zurückgegeben, und die Funktion alert zeigt das Ergebnis an, bevor dem Benutzer die Frage überhaupt gestellt wurde.

Ninox
var r:= #{:text
   setTimeout( () => {
      if (confirm("Are you ok ?"))
        return "of course";
      else
        return "Never !";
   }, 2000);
  return "I don't know";
}#;
alert( "Your answer is : "+r );

Bild.png

Wechselt man in den Modus callback (oder async, das ihm entspricht), so läuft die Ninox-Funktion alert erst dann, wenn der JavaScript-Code die Callback-Funktion aufgerufen hat – also nachdem dem Benutzer die Frage gestellt wurde. Der Typ steht immer an erster Stelle: :text:callback, nie :callback allein, das als Typ aufgefasst würde.

Ninox
var r:= #{:text:callback
   setTimeout( () => {
      if (confirm("Are you ok ?"))
         callback( "of course" );
      else
        callback( "Never !" );
   }, 2000);
   return "I don't know";
}#;
alert( "Your answer is : "+r );

Bildschirmaufnahme 2025-03-03 um 11.46.26.gif

Funktionsaufruf

Aus JavaScript heraus lassen sich Ninox-Funktionen aufrufen: Ein Block erhält die Funktionen, die weiter oben in derselben Formel deklariert sind. Die des globalen Codes sieht er nicht (siehe unten, „Eine Funktion des globalen Codes aufrufen“).

Ninox
function display( message : text ) do
    alert( message );
end;
#{ display('hello world') }#;

Bild.png

Um den Rückgabewert einer Funktion zu erhalten, müssen Sie wiederum den Callback-Modus verwenden (siehe oben: Asynchrone Ausführung):

Ninox
function add( a : number, b : number ) do
    a + b;
end;

#{:number:callback
   add( 1, 2, (returnValue) => {
     callback( returnValue )
   })
}#;

Bild.png

Wenn Sie nach dem Aufruf der Prozedur nichts weiter verarbeiten müssen, können Sie die Callback-Funktion direkt als Parameter übergeben:

Ninox
function add( a : number, b : number ) do
    a + b;
end;
#{:number:callback
   add( 1, 2, callback )
}#;

Bild.png

Eine Funktion des globalen Codes aufrufen

Eine Funktion des globalen Codes liegt nicht im Gültigkeitsbereich des Blocks: Sie dort über ihren Namen aufzurufen, führt zu einem Fehler. Rufen Sie sie über ninext.callNinoxFunction auf, mit null als zweitem Argument. Mit dieser Funktion im globalen Code:

Ninox
function hello(message : text) do
    alert(message)
end;

ruft eine Formel sie so auf:

Ninox
#{ ninext.callNinoxFunction('hello', null, 'hello world') }#;

Um den Rückgabewert zu erhalten, übergeben Sie als letztes Argument eine Callback-Funktion, die den etwaigen Fehler und dann das Ergebnis erhält. Mit der obigen Funktion add im globalen Code:

Ninox
#{:number:callback
   ninext.callNinoxFunction('add', null, 1, 2, (error, result) => callback(result))
}#;

Alles über callNinoxFunction: HTML-Hilfsfunktionen.

Datensätze und Funktionen bearbeiten

Wenn Sie eine Liste von Datensatz-IDs haben, können Sie eine Ninox-Funktion aufrufen, um jeden Datensatz einzeln zu ändern. Rufen Sie dazu einfach die Funktion auf und übergeben Sie ihr die ID.

Ninox
function setCustomerChecked( id : text, checked : boolean ) do
   var n := number(replacex(id, "\D",""));
   var rec := record( Customer, n );
   rec.Checked := checked;
end;
var c := select Customer;
var isChecked := not first(c).Checked;
#{
   c.forEach( v => { setCustomerChecked( v, isChecked ) } );
}#;
c.checked;

Ausführungsfehler

Tritt in Ihrem Code ein Ausführungsfehler auf, so wird der Text der Fehlermeldung zurückgegeben:

Bild.png

Im Modus callback wird genau diese Meldung zum Ergebnis des Blocks. Das obige Beispiel ist ein Syntaxfehler: Ab Version 3.2.0 meldet der Reiter „Fehler“ ihn vor jeder Ausführung (siehe „Was Ninext prüft“).

Beim Start: onNinextLoaded

Beim Start von Ninox wird der Trigger „After Open“ ausgeführt, bevor Ninext geladen ist: Native JS ist dort noch nicht einsatzbereit, und ein dort platzierter JavaScript-Block schlägt stillschweigend fehl.

Wenn Sie im Trigger „After Open“ eine Funktion onNinextLoaded(event) definieren, ruft Ninext sie auf, sobald es tatsächlich geladen ist – bei jedem Benutzer und bei jedem Öffnen der Datenbank. event.version liefert die Version von Ninext:

Ninox
function onNinextLoaded(event : any) do
   alert("Ninext " + event.version + " is ready");
   "… your JavaScript / NativeJS code here";
end;

So verfügen Sie über einen zuverlässigen Einstiegspunkt, um beim Start JavaScript auszuführen oder Ihre Datenbank an die Version von Ninext anzupassen.

Gut zu wissen

Ninox hat den Trigger „After Open“ beim Öffnen der Datenbank bereits ausgeführt; um onNinextLoaded aufzurufen, wertet Ninext seinen Code erneut aus. Ab Version 3.2.0 wertet es davon nur die Deklarationen erneut aus: die Funktionen sowie die Variablen (var, let), wenn eine Funktion eine davon liest. Die übrigen Anweisungen werden also nur einmal ausgeführt. Nur der Anfangswert einer Variablen, die von einer Funktion gelesen wird, wird ein zweites Mal berechnet: Lassen Sie ihn nicht von einem Schreibvorgang abhängen, etwa dem Anlegen eines Datensatzes. Im seltenen Fall, dass sich diese Deklarationen allein nicht kompilieren lassen, wertet Ninext den gesamten Trigger erneut aus, wie in früheren Versionen.

Bis Version 3.1.2 wertet Ninext den gesamten Code des Triggers erneut aus: Anweisungen außerhalb jeder Funktion werden dort ein zweites Mal ausgeführt. Berücksichtigen Sie das bei Anweisungen, die nur einmal ausgeführt werden dürfen: Anlegen eines Datensatzes, Meldung, Öffnen einer Tabelle.