10

Creative community and javascript

Hello everyone,

For more than a year I have been exploring the Ninox code to better understand how it works. Initially driven by the lack of documentation and the frustration of some limitations, I soon realised that Ninox is based on some very clever and efficient code.

After some time, I managed to get some JavaScript to run from NinoxScript by hooked the Eval function. I created a lot of functions that didn't exist before and had a lot of fun testing and understanding Ninox. I notice now that most of the functions I have created can be done in Ninox without using JS 🤭.

Today, I'm sharing some of this code with you in order to create a creative community that experiments with new features for Ninox (see badges on the files and comments tabs). The purpose of this is to let creativity and emulation express itself and that we can enrich each other with our discoveries.

WARNING: the code I share with you here is to be used exclusively for personal use. I do not recommend using it for commercial purposes.

Here, you have to put this HTML code in a formula on a page that is displayed first in your application. Once the code is executed, you can use Ninox's Eval function extensively. I have put two examples of extended functions here, but you can create your own later.

The first one allows you to execute some JavaScript code and get the return value. The second returns the list of comments for a record.

The code is commented out so that you can start to understand what is being implemented.

html("
<script>
    debugger;
    //get the address of the Eval function of NinoxScript
    var evalFunctor = queries.getFunctor('eval', [{ sign: function () { return 'string' } }, { sign: function () { return 'nid' } }]);

    //get the execution context of the NinoxScripts
    var Ctx = new queries.JSRuntimeContext();

    //setting up the hook function
    if (evalFunctor && Ctx /* && !evalFunctor.oldFunction*/) {
        //save the old eval function, the one with the parameters eval(string,nid)
        if (evalFunctor.argTypes[0] == 'string') {
            evalFunctor.oldFunction = Ctx.F[evalFunctor.functorId];
        }


        //initialization of new parameter types: eval(any,any)
        evalFunctor.argTypes[0] = 'any';
        evalFunctor.argTypes[1] = 'any';
        evalFunctor.sign = 'eval(any,any)';

        //creation of the new extended function table
        evalFunctor.exFunctions = {};

        //add extended functions to the array
        evalFunctor.exFunctions['exEvalJS'] = exEvalJS;
        evalFunctor.exFunctions['exComments'] = exComments;

        /*
            Implementation of the new function eval in the Ninox functions array.
            Now the eval function can be called in two ways.
            1 - In the case of a call to the original eval function:

                fnt: Ninox code to be evaluated.
                params: Current record (this) which will be used as a context to evaluate the fnt code.

            2 - In the case of a call to the extended functions:
                fnt: string that contains either the name of the extended function.
                params : object which contains the parameters of the extended functions call.

            in both cases :
                db : database object.
                ret : function to call at the end of the function. The functions are called by stacking. When the first one is finished, it calls the next one.

            */

        evalFunctor.hook = function (fnt, params, db, ret) {

            try {
                //search if the first parameter of the eval function strictly contains the name of an extended function
                if (evalFunctor.exFunctions[fnt]) {
                    //if this is the case, the corresponding function is called
                    evalFunctor.exFunctions[fnt](fnt, params, db, ret);
                } else {
                    //If not, the Ninox function is called
                    evalFunctor.oldFunction(fnt, params, db, ret);
                }
            } catch (err) {
                //in case of an error, the error message is returned through the 'ret' return function
                ret(String(err.message));
            }
        };

        //recording the hooked function in the NinoxScript function array
        Ctx.F[evalFunctor.functorId] = evalFunctor.hook;
    }

    //returns the record id from its number or from the record object
    function getId(record) {
        if (typeof record === 'string') return record;
        else if (typeof record === 'object') return record._id;
        else return null;
    }

    /*
    extended function that allows the execution of JavaScript code
    eval('exEvalJS', {javascript:, arguments:{param1:, param2:...}})
        javascript : string that contains the code to be executed
        arguments : {
                    param1,
                    param2… : parameters passed to the code as arguments to a function
                    }

    return : result of JavaScript fonction.

    exemple :eval('exEvalJS', {
                                javascript: 'return a + b;'',
                                arguments : {
                                            a: 10,
                                            b: 20
                                            }
                            });
    result -> 30;

    important: for an asynchronous function, call cb('return value') rather than return 'return value'

        exemple : promise.then( (value) => {
                                            cb(value);
                                            } ).then();

    */
    function exEvalJS(fnt, params, db, cb) {

        try {
            debugger;
            var { javascript, arguments } = params;
            var head = `var {${Object.keys(arguments).join(',')}} = args;`;
            var all = head + '\n' + javascript;
            var fn = Function('args', 'cb', all,);
            try {
                var Result = fn(arguments, cb);
                //check if function use CallBack to return result asynchronously
                if (javascript.search(/\b(cb)\b/) < 0)
                    return cb(Result);
            } catch (err) {
                var msgErr =
                    err.message + ' à la ligne ' + err.line - 2 + ', colonne ' + err.column;
                return cb(err)
            }
        } catch (err) {
            var msgErr =
                err.message + 'exEvalJS : à la ligne ' + err.line + ', colonne ' + err.column;

            cb(msgErr);
        }
    }

    /*
    getting the list of comments of a record
    eval('exComments', {id:} )
    return array of JSON with :
        comment : string of comment,
        userId : id of user post the comment,
        date : date of post in milli seconde.

    exemple : eval('exComments', {id:this.id});
    result -> {comment:... ,userId:..., date:167533002 }
    */
    function exComments(fnt, params, db, cb) {

        database.loadComments(getId(params.id), function (error, commentsArray) {
            if (error)
                cb('exComments : ' + error);
            else {
                var commentsList = [];
                for (num in commentsArray) {
                    var o = {
                        comment: commentsArray[num][2],
                        userId: commentsArray[num][1],
                        date: new Date(commentsArray[num][0]),
                    };
                    commentsList.push(o);
                }

                cb(commentsList);
            }
        });
    }
</script>
")

 

Copy

 

On Ninox, put this formula :

eval("exEvalJS", {
    javascript: "return a + b;",
    arguments: {
        a: 10,
        b: 20
    }
})

 

Copy

 

result -> 30

Be creative and have fun ! 😁

9 replies

null
    • Mel_Charles
    • 2 yrs ago
    • Reported - view

    Wow Jacques - thats brilliant stuff and if i may say so - very dedicated to the course!

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

    Following Uwe's request, I have created a database with some examples to use the EvalJS function which allows you to run javascript directly from Ninox. You can download it on GitHub at this address: https://github.com/JacquesTur/Ninext/blob/2f18b797803124825718157098fec7968466a296/demos/EvalJS/EvalJS.ninox

    Among the examples that may be useful to you, there is :
    - Ask value, which uses the prompt function to ask the user for a value,
    - copy, to copy a text or the content of a field to the clipboard.

    You can find inspiration on these two sites:
    https://www.w3schools.com or https://developer.mozilla.org (multi language).
    Have fun creating new features and share them with us 😁.

      • UweG
      • 2 yrs ago
      • Reported - view

      Jacques TUR Thank you Jaques
      A new playground opens up in Ninox

    • Gianluca
    • 2 yrs ago
    • Reported - view

    Jacques TUR @jacques I don't even dare to try to understand your scripts (😄) but I sure appreciate all the functionalities they add to Ninox! Congratulations!

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

    Following Alan Cooke  request, I made this JavaScript code which allows you to print several records. It is the equivalent of printRecord.

    And here is how to insert the JavaScript code into a global function to make it easier to use.

    function printRecords(arguments : any) do
        eval("exEvalJS", {
            javascript: "reports.openDesigner({ nid: id, nids: records, reportName: report, printAllAndClose: true })",
            arguments: arguments
        })
    end;
    
    

    Then just call the function like this:

    printRecords({
            id: string(this.ID),
            records: select Customer,
            report: "exPrintRecords"
        })
    

    the parameters are as follows: 

    • id: id of the current record,  
    • records: list of records to be printed,
    • report: name of the report to be printed.
    • Gunther.1
    • 2 yrs ago
    • Reported - view

    Hi, with the new way of Syntax -checking it's not possible anymore to create new code.

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

      Günther 

      On the web, everything seems to work fine. I don't have any problems. The syntax check is modified by the initialization code.
      If it doesn't work, it's probably because the initialization code wasn't executed.

      Furthermore, the this.Nr parameter might not be recognised in the context of a JSON variable. Indeed, this code is validated by the syntax analysis, but the variable "this" could not be recognised at runtime. It would be better to use an intermediate variable.

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

    Good evening to all, 

    I discovered two days ago 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')

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

    I worked a little bit to make it possible to use directly 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'), " | ")
    

    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.

    Have fun 😄

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

    I realize that I don't have put the link to the new thread about NativeJS. I separated it from this post so as not to confuse calls to JavaScript via the Eval function and NativeJS which allows you to embed JavaScript directly into Ninox code, access variables and functions and return values of any Ninox variable type, including "nid".
    For more informations, click here

Content aside

  • 10 Likes
  • 2 yrs agoLast active
  • 9Replies
  • 1226Views
  • 13 Following