Calling one Function from Another

I hope you can help me with this question. In the past, I could call a client-side function and it would have access to fields, variables, stack items, etc. If this client-side function calls another function, the other function is always run on the server.

The server-side function did not have access to the variables that were defined client-side. It is unclear whether on the server side the function had access to five.field.whatever, or stack items. My go-to way to handle this was always define a context object in the client function, load it with whatever was needed, and send that context to the server function.

I believe since the last version of five came out, variables defined client-side are now available server-side. Can you verify this is true?

Can you also verify if five.field.whatever and five.stack.whatever is also automatically available on the server function?

The reason I ask, is that some forms need to call a client function to set variables, and some forms need to call a client function to initialize certain tables with the desired values. But I was never able to do both from the same form (say on the On Show event). So I would have to modify one function to also include things that were done in the other function (depending on which form is being used).

For example, my SaveActiveFormInfo function does this:

function SaveActiveFormInfo(five, context, result)  {
    
    // assign constant values based on current form
    // assumes key field is named "{TableName}Key"
    const myForm = five.actionID();
    const myTable = five.getFive().getUserAction(five.getAction(myForm).key()).getForm().getDataSource().dataSourceId();
    const keyField = myTable + "Key";
    
    // see if the form is a setup form
    const setupForms = JSON.parse(five.getVariable('SetupForms'));
    const isSetupForm = setupForms.includes(myForm);

    five.setVariable('MyForm', myForm);
    five.setVariable('MyTable', myTable);
    five.setVariable('MyKey', five.field[keyField]);
    five.setVariable("IsSetupForm", isSetupForm);

    return five.success(result);
}

My InitSelectedTables function does this:

    const _five = five;
    const userKey = five.getVariable('UserKey');
    const myForm = five.actionID();
    const myTable = five.getFive().getUserAction(five.getAction(myForm).key()).getForm().getDataSource().dataSourceId();
    const keyField = "ReportsKey";
    const isSetupForm = true;
    
    five.setVariable('MyForm', myForm);
    five.setVariable('MyTable', myTable);
    five.setVariable('MyKey', five.field[keyField]);
    five.setVariable("IsSetupForm", isSetupForm);
    
    
    five.field.UserKey = userKey;
    five.field.TableName = myTable;
    
    // update groups
    let selectedTable = 'SelectedGroups';
    let myParms = {
        UserKey: userKey,
        SelectedTable: selectedTable,
        SelectedEntityKey: 'StudyGroupKey',
        SelectedEntityName: 'StudyGroupName',
        LookupTable: 'StudyGroups',
        LookupKeyField: 'StudyGroupsKey',
        LookupNameField: 'GroupName',
        JoinTable: 'ReportGroups',
        JoinKeyField: 'ReportKey',
        JoinKeyValue: five.field.ReportsKey,
        JoinEntityField: 'StudyGroupKey'
    };
    five.executeFunction('InitSelectedTableServer', myParms, null, '', '', function (result) {
        if (result.serverResponse.errorCode === 'ErrErrorOk') {
            five.refreshTable(selectedTable);
            five.reload();
            return;
        }
        let functionMessage = result.serverResponse.results;
        if (functionMessage !== '') {
            _five.showMessage(functionMessage);
        }
    });

You may notice that the part that sets the variables near the top is a duplicate of what is in the SaveActiveFormInfo function. That part was put in this function because I didn’t think the server environment would know of those field names and variables. So I had to duplicate that logic.

The last 2 sections of this function (setting context object and calling server function) is actually repeated multiple times for different tables. So the server function may initialize selected Members, selected Reports, selected StudyGroups, etc.

So this is getting very complicated calling client-side functions which sometimes want to call other client-side functions just so they will have access to fields, stack, variables, etc.

I am doing it this way because I’m not knowledgeable about what can be done client-side and what can be done server side.

So, can you help me obtain this knowledge?

Can a client function call other client functions, or are any functions executed FROM a client function ALWAYS run in the context of the server?

Exactly what, in the current version of Five, is available in the server environment? I’m referring to variables, fields, stack items, etc.

I have the feeling that I’m making my application way too complex, with way too many moving parts, simply because I don’t know what is possible or known on the server side.

If I haven’t totally confused you, can you help me with this?

Thanks so much…

Hi Ron,

If a client-side function 1 (ON events) needs to call another client-side function 2.

Assuming the functionality of client-side function 2 is generic (which can also be used by other functions; otherwise, only one function would be needed).

A library can be created that contains the functionality of client-side function 2.

When you call a library from a client-side function, behind the scenes (on execution time), the code of the library will be added to the client-side function.

I will provide the steps and an example of a library.

1 – Under the menu Logic, select the ‘Libraries’ option.

2 – Like a function, give a name and add the code:

The example below receives three parameters: five, the property name and value.

3 – In the ‘Functions’ menu, select the record you want the library to be attached to.

4 – In the tab ‘Libraries’, turn the switch ON for the library you want to call.

5 – Inside your client-side function, call the library like a standard function, passing all the parameters as defined in the library.

Regarding your questions:

Once you set a variable via a client-side function, you don’t need to reset it again in another client-side function (it will be available), unless you remove it or change its value.

The current version does not permit server-side functions to retrieve variables (using `five.setVariable`) that were set via the client-side function. If you have any different confirmation on this matter, please let me know.

We are working to implement this feature. In the meantime, I recommend that you send the variable through context, similar to how you call a server-side function with `five.executeFunction`.

Regards,

Elton S

Thanks so much Elton. Libraries are new to me. Related question: Are libraries ONLY able to be called by functions as in your example, or is it possible to trigger them from events also?

Tried your suggestion, and I must be doing something wrong.

I created a library called SetFormVariables.

I put this code inside:

function (five, context, result)  {

    five.log(five.actionID());
    const myForm = five.actionID();
    const myTable = five.getFive().getUserAction(five.getAction(myForm).key()).getForm().getDataSource().dataSourceId();
    const keyField = myTable + "Key";
    
    // see if the form is a setup form
    const setupForms = JSON.parse(five.getVariable('SetupForms'));
    const isSetupForm = setupForms.includes(myForm);

    five.setVariable('MyForm', myForm);
    five.setVariable('MyTable', myTable);
    five.setVariable('MyKey', five.field[keyField]);
    five.setVariable("IsSetupForm", isSetupForm);

    return five.success(result);
}

Notice the code comes up with a typical function signature of five, context, result instead of your example which you show five, variableName, value. I understand that was just an example of a library.

In my SetupReports function which used to set variables (among other things:

    const _five = five;
    
    SetFormVariables(five, null, null);
    
/*
    // set needed variables
    const myForm = five.actionID();
    const myTable = five.getFive().getUserAction(five.getAction(myForm).key()).getForm().getDataSource().dataSourceId();
    const keyField = myTable + "Key";
    five.setVariable('MyForm', myForm);
    five.setVariable('MyTable', myTable);
    five.setVariable('MyKey', five.field[keyField]);
    
    five.log('MyForm: ' + myForm);
    five.log('MyTable: ' + myTable);
    five.log('MyKey: ' + five.field[keyField]);

    // see if the form is a setup form
    const setupForms = JSON.parse(five.getVariable('SetupForms'));
    const isSetupForm = setupForms.includes(myForm);
    five.setVariable("IsSetupForm", isSetupForm);
    */
    const userKey = five.getVariable('UserKey');

I commented out the part that used to set the variables, and instead called the library.

Also, I turned on SetFormVariables in the Library tab of my function.

Now when I run it, I get

Now when I run this and try to open the form, I get:
Failed to display plugin: SetupReports

Can you please advise what I’m doing wrong? Thanks…

ALSO: I thought Jo told me earlier that the ability to use client-defined variables on the server was added in the last version update. My report queries, which were using {{five.variable.UserKey}} as parameters, wouldn’t work when generating them server-side, but would work when generating on-demand client-side. Jo said this was changed. Now the report works generated client-side AND server-side. The only thing I’m not sure about was whether I’m using a special beta version with this feature, or it is, in fact, working for the current release, using a client-side-defined variable. Could you ask Jo? Thanks!!!

Hi Ron,

The code you provided for the library is missing the name of the function, which is the reason you are receiving the error ‘Failed to display plugin: SetupReports’.

Please, add the name to your library.

function SetFormVariables(five, context, result) {

Note: If you use the properties ‘context’ and ‘result’ in your library, you must pass them from the function that is calling. Otherwise, you can remove them.

If you want to keep them, you can pass the properties when you call the library: SetFormVariables(five, context, result);

In general, the code of a library becomes an extension of the function that is calling; therefore, you can pass any parameter.

I noticed that in my example, I am returning five.success(result); but the result property has not been passed (Sorry, this was my mistake.)

Regarding the use of client-defined variables on the server, I will double-check internally to see if I made a mistake and get back to you.

Regards,

Elton S

Thanks Elton. Didn’t even notice this. Definite PEBCAK error!

On some of my forms which have subform pages (rendered as DataViews, from which I can click various items to flip it’s IsSelected property), I have action buttons for All and None. These call SetIsSelectedAll and SetIsSelectedNone functions respectively. Each of those functions build an almost-identical context object then call the SetIsSelectedServer function, which then executes the SQL to update all items on the page.

The only difference between the client functions is one adds a context item “IsSelected: true” and the other one adds “IsSelected:false”. Otherwise they are identical.

This seems like a waste of code. Is there a more efficient way to do this?

I can think of one: I can create a library to build the context object without the last IsSelected item, then call the library from SetIsSelectedAll (/ None), then simply add the needed last item in the function itself, then call the server function. This would clean up the code quite a bit.

If you agree this is a good way to do it, please advise how I pass the context object back from the library. The only way I know of is to save that object as a variable in the library, then use the variable to create a local object, then add the IsSelected item to the end. Is this correct?

If a library is not the proper way to do this, can you advise a better way?

Thanks…

Hi Ron,

In your scenario, a library should be the best approach.

If you want to return any value(s) to the function that is calling the library, you can just return them at the end of the library.

The library below returns an object that contains different data types.

(Note: In my example, I don’t need to use any parameter because I am formatting the dummy data myself).

Then, in the function that calls the library, I can retrieve the returned data and manipulate it via its properties.

Regards,
Elton S

Thanks for answering so quickly!

As a follow-up, can the library return an error in case it encounters one?

For example, consider the following code:

        let joinTableMap = {};

        try {

            joinTableMap = JSON.parse(five.getVariable('JoinTableMap') || '{}');

} catch (e) {

            msg = 'Error parsing JoinTableMap: ' + e.message;

            five.showMessage(msg);

return five.createError(result, msg);

}

        joinTable = joinTableMap[myForm]?.[subformName];

if (!joinTable) {

            msg = `No join table mapping found for form "${myForm}", subform "${subformName}"`;

            five.showMessage('error: ' + msg);

return five.createError(result, msg);

}

If I have code like this in my library, how would I code the calling function to handle the error? When calling a server function from a client function, it includes an embedded function where I could test Success, Error, etc.

Sorry for so many questions, but there is not really any documentation for libraries. Thanks…

Also sorry for combining questions in this topic. I’m leaving on vacation in 3 days and want to get as much done as I can. Is there a quick and easy way to get the field names from my join tables, given the join table name? This would be invaluable in avoiding a bunch of switch statements.

Only slightly un-related: What is the purpose of this statement, which appears in many functions you folks have helped me with?

const _five = five;

Hi Ron,

This new example of a Library that receives an option and, depending on its value, a different error message is formatted.

In my context, I don’t need to call five functions because the function that calls this library will handle the message error.

In the function that calls the library, pass the option and check the error value returned from the library, which was stored in the property “errorCode” accessed via “obejData.erroCode”.

This example will produce the result:

logs in the console.

Regarding the const _five = five;
It is only used when you have a callback function (client-side calling a server-side) that needs to use the five objects on the return of the callback (frontend).

Because when calling a server function, the client won’t wait for it to finish, therefore we store the five’s object into a variable so we can use any property or function in the frontend on the return of the server function (callback).

Hope this can help you.

Regards,
Elton S