Native JS

Native JS allows you to execute JavaScript code within a Ninox script and share functions and variables with it. This way, Native JS can read Ninox variables and call the functions declared in the same formula; functions of the global code, on the other hand, are called through ninext.callNinoxFunction (see below, “Calling a function of the global code”).

Native JS is on by default; it can be turned off in the Ninext Settings, "JavaScript Integration" section (Native JavaScript).

image.png

Limitations

Modifying a Ninox variable in NativeJS does not affect the Ninox variable.

JavaScript is executed only locally. Adding JavaScript to code that runs on the server has no effect. Likewise, calling a global function that contains JavaScript from code executed on the server has no effect. From version 3.2.0 onwards, the Errors tab reports a block placed directly in a table trigger (after create, after update) or in a do as server.

See here for the codes that are executed locally and on the server :

image.png

Syntax

Execution

To specify to Ninox that you are switching to JavaScript, you need to enclose the code within #{ ... }#:

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

Remember, Ninox is a pre-compiled language that needs to know the type of each variable and function in advance. The same applies to Native JS: you must specify the type returned by the JavaScript code. By default, Native JS returns text, but you can indicate the type of variable you want to return by specifying it at the very beginning of the code with the notation :[type].

The annotation is stuck to #{, without any space: #{:number …, not #{ :number …. Likewise, two annotations follow each other without a space (:text:callback). With a space, the annotation is not recognized: the block is typed as text, and the annotation stays in the JavaScript code, where it causes a syntax error.

Return value

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

The possible types are: text, number, date, timestamp (date and time), time, timeinterval, appointment, boolean, html, color, icon, email, phone, location, file, user, json and any. The complete list, with choice, link, style and styled, is offered by the Console (see below).

Beware

A date with a time is annotated :timestamp, whereas a function parameter of the same type is declared datetime. :datetime does not exist as an annotation: the block would be typed as text. From version 3.2.0 onwards, the Errors tab reports an unknown annotation.

Dates, times and durations are exchanged in milliseconds: a Ninox variable of this type arrives in the JavaScript as a number, and a :date or :array(date) block must return milliseconds (which is what getTime() gives for a JavaScript date).

Array types

To return an array of values, give the type of its elements with :array(type) (from version 3.2.0 onwards):

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

All simple types are possible: :array(text), :array(date), :array(any)…

Up to version 3.1.2, only an array of type any can be returned. This notation remains valid, as an equivalent of :array(any):

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

The :number(array) notation, which this page used to show, does not return an array. Since version 3.2.0, the Errors tab reports it.

Record types

To return records, use nid or rid, followed by the table name in parentheses:

If you use nid, you must return an array of text values containing the full record identifier, which includes the table identifier + record number:

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

If you use rid, you should return the record number :

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

For compound table names, do not add ' as in Ninox, but simply use the full name of the table :

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

By using this syntax, Native JS returns variables that behave the same way as Ninox code. This means you can use the following syntax as if it were a select :

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

image.png

Typing help in the Console

From version 3.2.0 onwards, the Console helps you write a block:

  • in the annotation: when you type : right after #{, it suggests the return types, array(type), nid(Table) and rid(Table); when you type ( after nid or rid, the list of tables, and after array, the list of types; a second : suggests the callback and async modifiers;
  • in the JavaScript: autocompletion suggests the Ninox variables and functions the block receives, in their JavaScript form, then, after a dot, the object's members — for a record, the names of its fields (see “JavaScript autocompletion in native blocks” on the Console page).

This help only exists in the Console.

What Ninext checks

From version 3.2.0 (beta) onwards. The Errors tab checks the #{ … }# blocks of the whole database, and the Console those of the code you write there.

The following are reported:

  • a JavaScript syntax error;
  • an unknown annotation (such as :datetime) or a table not found in :nid(…) or :rid(…): the block would be typed as text;
  • a typed block without any return, or with some paths that reach none: it would return an empty value;
  • a :callback or :async block that never calls callback: the formula would never get a value;
  • a block placed directly in a table trigger (after create, after update) or in a do as server, where it does not run.

Record modification

Record-type variables are passed in JSON format, with a key-value pair for each field of the record.

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

Here, the content of the variable r in JavaScript looks like this :

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"
}

The object also carries a _node key: the record as Ninox stores it, its fields designated by their internal identifiers.

Important

Depending on whether the code is executed on the Mac application or on the web, the ID may have different casing.
To address this and avoid conflicts between different platforms, the ID is repeated with all possible casing variations.

Modifying the record in JavaScript does not update it in Ninox.

Asynchronous Execution

Sometimes, it is useful to synchronize the execution of Ninox code with JavaScript.
For example, when using a timer, it is important that the code only completes after the timer has finished executing.
Without this synchronization, the code following the JavaScript execution would run immediately, potentially causing unexpected behavior.

In this first case, the value “I don’t know” is returned immediately, and the alert function displays the result before the question is even presented to the user.

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 );

image.png

By switching to callback mode (or async, which is equivalent to it), the Ninox alert function executes only after the JavaScript code has called the callback function, meaning after the question has been presented to the user. The type always comes first: :text:callback, never :callback alone, which would be taken for a type.

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 );

Enregistrement de l’écran 2025-03-03 à 11.46.26.gif

Function Call

It is possible to call Ninox functions from JavaScript: a block receives the functions declared higher up in the same formula. It does not see those of the global code (see below, “Calling a function of the global code”).

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

image.png

To retrieve the return value of a function, you must again use callback mode (see above: Asynchronous Execution):

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

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

image.png

If you have no further processing to do after calling the procedure, you can directly pass the callback function as a parameter:

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

image.png

Calling a function of the global code

A function of the global code is not in the block's scope: calling it by its name there causes an error. Call it through ninext.callNinoxFunction, with null as the second argument. With this function in the global code:

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

a formula calls it like this:

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

To retrieve the return value, pass a callback as the last argument, which receives the possible error then the result. With the add function above placed in the global code:

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

Everything about callNinoxFunction: HTML helping functions.

Manipulate record and function

If you have a list of record IDs, you can call a Ninox function to modify each record one by one. To do this, simply call the function and pass it the 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;

Execution errors

If an execution error occurs in your code, the error message text will be returned:

image.png

In callback mode, this same message becomes the block's result. The example above is a syntax error: from version 3.2.0 onwards, the Errors tab reports it before any execution (see “What Ninext checks”).

At startup: onNinextLoaded

When Ninox starts, the "After Open" trigger runs before Ninext is loaded: Native JS is not yet operational there, and a JavaScript block placed there fails silently.

If you define an onNinextLoaded(event) function in the "After Open" trigger, Ninext calls it once it is truly loaded, for each user, every time the database is opened. event.version gives the Ninext version:

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

This gives you a reliable entry point to run JavaScript at startup, or to adapt your database to the Ninext version.

Good to know

Ninox has already run the "After Open" trigger when the database opened; to call onNinextLoaded, Ninext evaluates its code again. From version 3.2.0 onwards, it only evaluates its declarations again: the functions, and the variables (var, let) when a function reads one of them. The other statements therefore run only once. Only the initial value of a variable read by a function is computed a second time: do not make it depend on a write, such as creating a record. In the rare case where these declarations do not compile on their own, Ninext evaluates the whole trigger again, as in earlier versions.

Up to version 3.1.2, Ninext evaluates the whole code of the trigger again: statements placed outside any function are executed a second time. Keep this in mind for those that must only run once: creating a record, showing a message, opening a table.