Can't get result from server function into my client function

I have a function called FlipDataViewItemIsSelected:

function FlipDataViewItemIsSelected(five, context, result)  {
    
    five.log('FlipDataViewItemIsSelected');
    const _five = five;
    const myForm = five.getVariable('MyForm');
    const myKey = five.getVariable('MyKey');

    const subformName = five.getVariable('SubformName');
    const ctxResult = GetSelectedJoinTableContext(five, subformName);
    const myParms = ctxResult.context;
    
    // collect new TO email list if wanted
    if (myForm === 'GenerateEmails' && myKey === 'GENERIC') {
        myParms.ReturnSelectedEmails = true;
    }

    five.executeFunction('SetIsSelectedServer', myParms, null, '', '', function (result) {
        five.log(`client: result.serverResponse=${JSON.stringify(result.serverResponse)} `);
        // handle error
        if (result.serverResponse.errorCode !== 'ErrErrorOk') {
            const functionMessage = result.serverResponse.message || 'Unknown server error';
            _five.showMessage(functionMessage);
            return;
        }
        
        const toList = result.serverResponse.ToList || '';
        if (toList) {
            five.log(`Send to: ${toList}`);
        } else {
            five.log('No TO list returned.');
        }

    });
    
}

it calls SetIsSelectedServer:

function SetIsSelectedServer(five, context, result)  {
    
    /*
    modify the 'IsSelected' field in context.SelectedTable table as follows:
        if context contains an 'IsSelected' member: use its value to set 'IsSelected' in all records for context.UserKey
        otherwise: set 'IsSelected' to context.SelectedValue for record identified by context.UserKey and context.SelectedField

    if context.IsSetup = true, update the permanent join table as follows:
        first, delete all records from context.JoinTable for context.LeftValue
            ex: DELETE FROM BoardReports WHERE BoardPositionKey = '{CurrentLeftFieldValue}'
        second, add records back into context.JoinTable for 'selected' records in context.SelectedTable for this user and 'Right' field value
            ex: INSERT INTO BoardReports (BoardPositionKey, ReportKey) SELECT '{CurrentBoardPositionKey}', ReportKey
                FROM SelectedReports WHERE UserKey = '{CurrentUserKey}' AND IsSelected = true
    
    expected context:
    {
        UserKey: five.getVariable('UserKey'),
        IsSetupForm: true | false,
        IsSelectAllForm: true | false,
        IsSelectNoneForm: false | true,
        IsUseDefaults: isUseDefaults,
        SelectedTable: selectedTable,
        SelectedEntityField: selectedField,
        SelectedEntityName: selectedEntityName,
        SelectedValue: selectedValue,
        JoinTable: joinTable,
        JoinLeftField: joinLeftField,
        JoinLeftValue: joinLeftValue,
        JoinRightField: joinRightField,
        JoinRightValue: joinRightValue,
        IsSelected: true|false|missing, - (optional, see first note above)
        ReturnSelectedEmails: true|false|missing (optional, return selected member emails if truthy)
    };
    */

    let sql;
    let phase = 'starting';

    try {

        // determine whether to set all records for UserKey or flip a single record
        if (context.IsSelected !== undefined) {
            phase = 'set all IsSelected';
            sql = `UPDATE \`${context.SelectedTable}\` SET \`IsSelected\` = ? WHERE UserKey = ?`;
            five.executeQuery(sql, 0, context.IsSelected, context.UserKey);
            // five.log(`SetIsSelectedServer: ${phase}, userKey=${context.UserKey}, IsSelected=${context.IsSelected}`);
        } else {
            phase = 'flip single IsSelected';
            sql = `UPDATE \`${context.SelectedTable}\` SET \`IsSelected\` = NOT \`IsSelected\` 
                   WHERE UserKey = ? AND \`${context.SelectedEntityField}\` = ?`;
            five.executeQuery(sql, 0, context.UserKey, context.SelectedEntityValue);
            // five.log(`SetIsSelectedServer: ${phase}, userKey=${context.UserKey}, selectedValue=${context.SelectedEntityValue}`);
        }

        // update join table if setup form
        if (context.IsSetupForm) {
            phase = 'update join table';

            // delete existing join records
            sql = `DELETE FROM \`${context.JoinTable}\` WHERE \`${context.JoinLeftField}\` = ?`;
            five.executeQuery(sql, 0, context.JoinLeftValue);

            // insert selected records for this user
            sql = `INSERT INTO \`${context.JoinTable}\` (\`${context.JoinLeftField}\`, \`${context.JoinRightField}\`)
                   SELECT ? AS \`${context.JoinLeftField}\`, \`${context.SelectedEntityField}\`
                   FROM \`${context.SelectedTable}\`
                   WHERE IsSelected = true AND UserKey = ?`;
            five.executeQuery(sql, 0, context.JoinLeftValue, context.UserKey);
            five.log(`SetIsSelectedServer: ${phase} completed`);
        }

        // optionally rebuild the TO list if requested
        if (context.ReturnSelectedEmails) {
            phase = 'rebuild TO list';
            sql = `SELECT m.FullName, m.Email FROM SelectedMembers AS sm 
                    INNER JOIN Members AS m ON m.MembersKey = sm.MemberKey 
                    WHERE sm.UserKey = ? AND sm.IsSelected = true 
                    AND m.Email IS NOT NULL AND m.Email <> '' 
                    ORDER BY m.SortName`;
            const queryResult = five.executeQuery(sql, 0, context.UserKey);
            const rows = queryResult.values || [];
            const toList = rows.map(r => `${r.FullName} <${r.Email}>`).join('; ');
            five.log(`server: toList: ${toList}`);
            result.ToList = toList;
            // return five.success(result);
        }
        return five.success(result);

    } catch (e) {
        five.log(`SetIsSelectedServer ERROR during "${phase}": ${e.message}`);
        return five.error(`Error during "${phase}": ${e.message}`);
    }

}

Right now, I’m only concerned with the code inside the IF block near the end. I want to create a string of email addresses and return them to the client. the query works, and the server function properly logs the result.

But I can’t seem to get the returned text to appear properly in the client function. I’ve tried many different ways using the result object. the only way I can get it to be seen by client is to just return the string in the message property. but that causes a message box / alert to pop up in the client UI. So can you please help me with this? ChatGPT thinks it’s smart enough to answer this, but not really. I get a lot of help from that, but it doesn’t know Five.

Can you help please? Thanks…

Hi Ron,

Thank you for bringing up this question.

In your server-side function, you need to format the return instead of just returning “five.success(result)“. This is one example of how to do it, in which I returned the total number of inserted records:

//Create an object to store the inserted data + any error message
const returnData = {

insertCount: insertCount,
errorMessage: errorMessage

};

// Format five results and the date to be sent back to the client function.

result = five.success(result);

result.Results = JSON.stringify(returnData);

result.Code = five.ErrErrorOk;

result.Message = “”;

return result;

In this example, I have created an object that will contain the result (data). Then I finish the format of the result property, and I return it to the client function.

Back in the client function, you can parse the response, evaluate it and perform the logic your application needs.

const response = JSON.parse(result?.serverResponse?.results);

if (response.insertCount && response.errorMessage.length === 0) {

//Do something

} else {

//Do something else

}

You can also log the reponse in the client to see the return/response from the server.

Please let me know if you have any questions.

Regards,
Elton S

Thanks so much Elton. That worked, and got me to having the value in the client function. I now see the field logged.

I’d like to put the result in a field. I have a field called “To”, which is like the “To” in a new email message.

I tried:

        const response = JSON.parse(result?.serverResponse?.results);
        const toList = response.ToList;
        five.log(`client: toList=${toList}`);
        five.field.To = tolist;
        five.reload();

That didn’t work, although the value does get logged. is there something I’m missing? Thanks…

PS: the client function is running in the context of the Members page subform, which is powered by a DataView. The field I need to update is the “To” field on the General page. I guess that needs to be taken into account in the solution?

PPS: just to clarify, the General page has fields like To, Cc, Bcc, Subject and Body. these are not “real” fields (there is nothing in the Field line when designing the field). I thought that it would work like a screen field. I’d rather not add all those fields to the underlying table, because I don’t want to keep these values. I only want the values to last until I click the “Send” button.

Should I be using a process instead? I think not, because the process doesn’t allow subform pages the way a form does. I want to be able to choose members from the Members page (Data View powered) and have the email list text string obtained from the SelectedMembers table (which the DataView successfully updates), and that should appear in the To textbox on the General page. also want the user to be able to add miscellaneous emails to any generated email to string in the field, by typing something like “; someone@example.com”. Am I being too ambitious?

I’ve moved the last part of this to a new topic, Trying to Automate Emails, in the interests of keeping each posting to a single topic. Please see that topic.

Hi Ron,

Normally, when you execute a callback function, the server call runs asynchronously. This means that by the time the server function returns a response, the client-side function has already finished executing. Because of this, the code needs to be structured slightly differently to handle the returned value correctly.

In your scenario, you’ll need to adjust your code to account for this asynchronous behaviour. The example below demonstrates how you can retrieve a value from the backend and assign it to a field on the form.

Important: For this to work, the form must already be in Edit mode.

Notes:

  1. In this example, the form field that receives the value is called FieldTo. You should replace this with the name of your own field.

  2. The function dataManager.setValueByName() assigns the value returned from the server to the form field.

  3. The function dataManager.setModifiedField(field.formField.key()) marks the field as modified. This simulates a user change and enables the Save button on the form.

  4. The function field.refresh() refreshes the field, ensuring that the latest value is always displayed in the UI.

function GetTotalProductClient(five, context, result) {

    var variableObj = {};

    const field = five.sender();

    const dataManager = field.dataManager;

    five.executeFunction(“GetTotalProductServer”,variableObj, null, null, null, (result) => {

        try {

            const response = JSON.parse(result?.serverResponse?.results);

            if (response.recordCount && response.errorMessage.length === 0) {

                console.log(response);
                dataManager.setValueByName('FieldTo', response.recordCount);
                dataManager.setModifiedField(field.formField.key());
                field.refresh();

            } else {

                console.log('No data has been returned');

            }

        } catch (err) {

            console.log(err);

        }

    });

}

Thanks for the quick reply Elton. This seems quite restrictive. I’m just trying to build an email sending form. Didn’t want to have to put the form into edit mode if I didn’t need to. Could you please answer the following questions?

ChatGPT is telling me that since I’m selecting the TO addresses from a form page powered by a DataView click event that calls a client side function, that it couldn’t update a field on the general page. It wouldn’t have knowledge of those fields. Is that true?

Is there a way for function code to put the form into edit mode, or must I manually click something?

Should I instead use a process instead? If I do, I may need to jump to a form to choose members to send emails to, then return to the process to fill in the email addresses I collected on the other form. I know I can do this from a form by using previous action. Does this also work going back to a process? Process would need to have an actionID.

I hope you can help me with this.

Thanks…

PS: I also tried modifying the callback to simply set a variable, then attached an event on the General page OnSelecting event to assign the variable value to the field, but that didn’t work either. Since I attached the event to that page, it should avoid any issues of the server function not yet having completed. the function i attach logs the value, then sets the field to that value. I see the log, but the field is still empty.

If it is not possible to put the form in edit mode using code, then this is not a great solution.

How about the idea of using a process instead, and when i click a button, it runs a function which opens a selection form to choose members, then comes back to the process and fills in the valuse?

I know previousAction can take you back to a form, but can it take you back to a process also? If so, that would be very helpful. It would be good if on the process I can make some things visible or invisible depending on a known value, the way we can do with form controls.

Do you think that would work?

Hi Ron,

I Have replied on this question: Trying to Automate Emails - How do I? - Five | Community Forum

Please let me know if my response has resolved this issue.

Regards,
Elton S

This issue is now resolved. I will continue in the other thread if more is needed. Thanks!!!