4

Ninext : Native JavaScript

I start here a new post about the Native JavaScrip to differentiate it from the EvalJS function in which I had previously put it...

I discovered one weekago that it was possible to add comments in Ninox. In fact, this notation was originally intended to be able to add JavaScript code directly into the Ninox code. The structure of the function is there, but it was left as a skeleton.

I just added a NativeJS module in the Ninext project (see Ninext project) which gives the possibility to add JavaScript blocks in the Ninox script. As an example, this is what it looks like :

function copyToClipboard(value : text) do
    #{
    navigator.clipboard.writeText(value);
    alert( '"'+ value + '" copied on clipboard' );
    return value;
    }#
end;

copyToClipboard('User value')

Copy

Everything between #{...}# is in JavaScript, the rest is Ninox code.

I worked a little bit to make it possible to use directly Ninox variables and function parameters in the JavaScript. It is also possible to define the type of variable that return by the JavaScript code. This allows, for example, to manipulate variables of type NID (list of records) and to retrieve the value as if it had been created by a Select function.

This example takes a list of records sorted in an ascending way (the only one that is possible on Ninox) and reverses the sorting to make a descending list :

var asc := (select Customer order by 'First Name');
var desc := asc;
join(#{:nid(Customer) return desc.reverse()  }#.('First Name' + " " + 'Last Name'), " | ")

Copy

join(#{:nid(Customer) return desc.reverse()  }#.('First Name' + " " + 'Last Name'), " | ")

It is this notation at the very beginning of the JavaScript block that defines the type of value returned. Here we ask that the result be a list of entries in the Customer table.

It is important to understand that Ninox is a precompiled code that determines in advance the nature of the returned variables, whereas JavaScript is interpreted in real time and we cannot predict in advance what type of value will be returned (unlike TypeScript). To switch from one world to the other, a minimum of definition is required. 
By default, the type returned by a JavaScript block is Text.

It is also important to remember that this code only works if the Ninext project is initialized. The Ninext project is only initialized locally on the Mac application, iPhone IPad or on the local web browser. It is not initialized on the Ninox server. So if you use JavaScript blocks, you have to make sure that they will never be executed on the server, for example by API calls that would read the value of a formula (only the eval/exec function of the API can read the value of a formula).
If ever a block is executed on the server (or locally when Ninext is not initialized), the block is considered as a comment, as if it was not there.

I've attached an example database that I hope will help you get started with JavaScript block integration.

...
Since last week when I posted this text, I've made some changes in NativeJS :

- It is now possible to call a Ninox function directly from NativeJS,
- Added variable from For loop for i in select Customer do #{ var a = i['First Name']}#...
- It is now possible to have direct access to field values from a variable of Node type  (var t := this #{t.Code = ... t['User value'] = ... }#...
 - I added examples in the database
- CodeMirror is now the  interface to enter the code to with syntax highlighting (but not auto-completion for the moment)

Have fun 😄

12 replies

null
    • Ninox developper
    • Jacques_TUR
    • 2 yrs ago
    • Reported - view

    I just put a small patch online: version 1.0.3.

    I found a bug that occurred when NativeJS code called a global function. It concerned the management of variable scopes which was not respected. NativeJS tried to access variables out of scope and this caused a bug that prevents the execution of the code. This is now fixed. 

    • Sean
    • 2 yrs ago
    • Reported - view

    Jacques TUR Hi Jacques, I downloaded your sample database and copied the code from the Run Code button to Trigger after update in User Value. It didn't run when I changed the value in that field. Do you have any plans, or is it even possible, to make it work in triggers? Thanks

      • Ninox developper
      • Jacques_TUR
      • 2 yrs ago
      • Reported - view

      Sean Yes, this is normal. The After update trigger code runs on the server, not locally. And on the server, Ninext does not work, it is loaded only locally. 

      This page of the documentation shows the execution contexts of some triggers and functions. We see that the triggers On new record and After update are executed on the server. Just like the select function. In these cases, the NativeJS code is simply considered as a comment.

      • Sean
      • 2 yrs ago
      • Reported - view

      Jacques TUR Ok, but in my case I am using the Native app so that would mean it's executed on the Client according to their table. I don't know their code like you do so I'm relatively clueless to what's involved.

      • Ninox developper
      • Jacques_TUR
      • 2 yrs ago
      • Reported - view

      Sean It doesn't work locally either. I think I know the explanation: 

      On the web, there is a script interpreter locally and one on the server. When the script is interpreted locally, Ninext works. On the server it does not work, because it is not possible to modify the interpreter on the server.

      In the Mac application, there are also 2 interpreters: the local interpreter and the server interpreter which is in the application code. One could say that it is local, but in reality it is separated from the local code and Ninext does not have access to it. 

      It is this structure that is identical in the Mac application and on the web that allows the Mac application to work offline or connected to databases on the web.

      If you want, send me a private message to explain what you want to do. Maybe I can find a way to do it without going through After update.

      • Sean
      • 2 yrs ago
      • Reported - view

      Jacques TUR Thank you for your time and explanation 🙏. I’m going to try a different approach and if I get stuck I will contact you.

    • Ninox developper
    • Jacques_TUR
    • 2 yrs ago
    • Reported - view

    Hello everyone, 

    I improved NativeJS to fix one bug with the date type return value. Now it works fine and you can get any date type like this:

    #{:date return Date.now()}# 

    return a Ninox date value : 22/11/2022 

     

    var myDate := now()
    "on "+ #{:date return myDate}# + " at " + #{:time return myDate}#
    

    return a string with date + time : on 22/11/2022 at 21:09

    NativeJS version 1.0.4

    • Michael.9
    • 1 yr ago
    • Reported - view
    Jacques TUR said:
    Ninext

     Hello Jacques TUR! I have already read your article regarding Java Script. It was very interesting to me but I am not a software developer therefore it was hard to understand.

     

    With Ninox I created an invoice management and at the end of the process I create a file which is stored in the database. I can download the file manually and send them to bank via online banking.

     

    is there a possibility with your Java Script to download the file directly automated in the FileSystem of the computer?

     

    looking forward to hear from you shortly

     

    Michaek

      • Ninox developper
      • Jacques_TUR
      • 1 yr ago
      • Reported - view

      Michael Yes, it is. I do it for myself. The first version was shorter, but not compatible with all browsers. I tested this version on Mac with Safari, Chrome and FireFox.

      "----------------------------
      Download file
      -----------------------------";
      function downloadFile(fileNinox : file,destFileName : text) do
          #{:text:callback
      // separate the table id, the record number and the file name
      var file = util.parseNIDFile(fileNinox);
      // If the destination of the file name is not defined, it is set as the current name by default.
      if (!destFileName) destFileName = file[2];
      // create the URL of the file
      database.downloadURL1(fileNinox)
          .then((fileURL) => {
              fetch(fileURL)
                  .then(resp => resp.blob())
                  .then(blob => {
                      const url = window.URL.createObjectURL(blob);
                      const a = document.createElement('a');
                      a.style.display = 'none';
                      a.href = url;
                      a.download = destFileName;
                      document.body.appendChild(a);
                      a.click();
                      window.URL.revokeObjectURL(url);
                      callback(url.url);
                  })
          });
      }#
      end;
      

      Put this function in the global functions to use it everywhere. Then call it with the file to be uploaded and the destination name :

      downloadFile(myFileField, "invoice.pdf");
      

      or

      var myFile := first(files(this));
      downloadFile(myFile, "");
      

       

      But, maybe, you can send an email from Ninox with the attached file (see here)?

      • Ninox developper
      • Jacques_TUR
      • 1 yr ago
      • Reported - view

      Note: each browser seems to have a different behavior when downloading a file with this function. Safari replaces the file if the name already exists. Chrome adds a number in parentheses to the end of the file name. And FireFox adds a dash and a number at the end of the file name and opens the file in the browser. 

    • Michael.9
    • 1 yr ago
    • Reported - view

    Thank you a lot Jaques TUR,

     

    I put the first code into global function as you mentioned. Then I tried to modify my code to implement the download of the file, but both ways you mentioned return an error: function not defined downloadFile(file, string). Here is my code:

     

    let myBNK := 'BNK-Lauf-ID';
    let myZLDat := format(now(), "YYYY-MM-DD_HHmm";
        let myChoise := dialog("Buchungs-ID", "Ist die Buchungs-ID: " + myBNK + " noch die aktuelle?", ["ja", "nein"]);
        if myChoise = "nein" then
            alert("Bitte zuerst die Banklauf-ID manuell aktualisieren!")
        else
            if 'Zahlaufträgedatei erstellt?' = 1 then
                alert("Bitte noch nicht übertragene Dateien auswählen!")
            else
                let myZDatei := 'Zahlaufträgedatei erstellt?';
                let myArray := (select Zahllauf where 'Z-Datei erstellt' = myZDatei and Gesperrt = 0);
                let myRow1 := "Transferart;Ausgabeart;Eigenkontoname;Auftragsunterart;Eigenkonto-BLZ/BIC;Eigenkonto-Nummer;Fremdkonto-BLZ/BIC;Eigenkonto-Inhaber;Fremdkontoname;Fremdkonto-Nummer;Fremdkonto-Inhaber;Verwendungszweck;DM-Betrag;Angelegt;Geplant;Späteste Ausführung;Ausgeführt;Skonto bis;Gebucht;Wertstellung;Fremdkonto-Inhaber;Skonto DM;Mehrfachausführung;Anzahl Ausführungen;Modus;Zusatzdaten;Ausstellungsort;Schecknummer;Textschlüssel;Memotext;Gesperrt;TAN1;TAN2;Angelegt von;Zuletzt geändert von;zuletzt geändert am;Primanota;Kategorisierungen;Betrag EUR;Auftragswährung;Skonto EUR;Umsatztext;1. Ausführung;externe AuftragsID;letzte Ausführung;Ende-zu-Ende-Referenz" + "
    ";
                let myRows := for i in myArray do
                        i.Transferart + ";" + i.Ausgabeart + ";" + i.Eigenkontoname + ";" + i.text(Auftragsunterart) + ";" + i.'Eigenkonto-BLZ/BIC' + ";" + i.'Eigenkonto-Nummer' + ";" + i.'Fremdkonto-BLZ/BIC' + ";" + i.'Eigenkonto-Inhaber' + ";" + i.Fremdkontoname + ";" + i.'Fremdkonto-Nummer' + ";" + i.'Fremdkonto-Inhaber' + ";" + i.Verwendungszweck + ";" + i.'DM-Betrag' + ";" + i.Angelegt + ";" + i.Geplant + ";" + i.'Späteste Ausführung' + ";" + i.'Ausgeführt' + ";" + i.'Skonto bis' + ";" + i.Gebucht + ";" + i.Wertstellung + ";" + i.'Fremdkonto-Inhaber' + ";" + i.'Skonto DM' + ";" + i.'Mehrfachausführung' + ";" + i.'Anzahl Ausführungen' + ";" + i.Modus + ";" + i.Zusatzdaten + ";" + i.Ausstellungsort + ";" + i.Schecknummer + ";" + i.'Textschlüssel' + ";" + i.Memotext + ";" + i.text(Gesperrt) + ";" + i.TAN1 + ";" + i.TAN2 + ";" + i.'Angelegt von' + ";" + i.'Zuletzt geändert von' + ";" + i.'Zuletzt geändert am' + ";" + i.PrimaNota + ";" + i.Kategorisierungen + ";" + i.'Betrag EUR' + ";" + i.'Auftragswährung' + ";" + i.'Skonto EUR' + ";" + i.Umsatztext + ";" + i.'1. Ausführung' + ";" + i.'externe AuftragsID' + ";" + i.'letzte Ausführung' + ";" + substr(i.'Ende-zu-Ende-Referenz', 0, 32) + "
    "
                    end;
                let myFileName := format(now(), "YYYY-MM-DD_HHmm") + " " + myBNK + " " + "Zahllauf" + ".csv";
                let myCSV := createTextFile(this, myRow1 + myRows, myFileName);
                var myFile := first(files(this));

                downloadFile(myCSV, "");
                'Anzahl der Zahlungstransfers' := cnt(myArray);
                'Letzter ZL am' := now();
                Gesamtwert := sum(myArray.'Betrag EUR');
                let newBLP := (create 'Bankenlauf-Protokoll');
                newBLP.(Datum := today());
                newBLP.(Anzahl := cnt(myArray));
                newBLP.(Gesamtbetrag := sum(myArray.'Betrag EUR'));
                newBLP.(ID := myBNK);
                alert("Es wurden " + cnt(myArray) + " Datensätze mit einem Transfervomumen von " + sum(myArray.'Betrag EUR') + " EUR verarbeitet. Bitte die Zahlungsträgerdatei herunterladen und im Filesysem <Dokumente/Zahlungsträgertransfer> ablegen, damit MacGiro sie importieren kann.")
            end;

    With the bold marked code I tied the download, I guess I didn't adapted your code in the right was. Do you have an idea to improve?

     

    Happy new year

     

    Michael

    • Ninox developper
    • Jacques_TUR
    • 1 yr ago
    • Reported - view

    Please read this message about the Ninext shutdown : https://forum.ninox.com/t/m1h0b4d/ninext-important-information

Content aside

  • 4 Likes
  • 1 yr agoLast active
  • 12Replies
  • 1564Views
  • 8 Following