[ADDED in 2.52]Sending JSON Data in API Caller Body with CrossUI

Post new features wanted for Builder or Designer here
Post Reply
romain
Posts: 30
Joined: Wed Jan 31, 2024 3:24 pm

[ADDED in 2.52]Sending JSON Data in API Caller Body with CrossUI

Post by romain »

Hello CrossUI Community,

I've been leveraging CrossUI for a week - it's incredibly versatile. I'm currently trying to send JSON data through the API Caller's body instead of the URL query. Is there a way to achieve this directly in CrossUI, or must I resort to crafting a custom JS function for AJAX requests?

I'd like to pass in body this JSON :
{
"intitule_el" : { "Value" : "asdvsvsbcd" } ,
"truc_el" : { "Value" : "asdvsvsbcd" }
}
instead of having it in querystring in url :
.../api/elements?_rand_=ertl7wxb&intitule_el=asdvscscbcd&truc_el=asdvsvsbcd

Looking forward to your guidance. Thanks!
support
Posts: 18
Joined: Thu Jan 25, 2024 4:58 pm

Re: Sending JSON Data in API Caller Body with CrossUI

Post by support »

If you want to set query data, use queryData ( not in the "Web API Caller" dialog), which you can find it in the right "Component Config" area.

And set queryMethod to "POST".

requestType should be "FORM" or "JSON".

Will enhance "Web API Caller" dialog later.
romain
Posts: 30
Joined: Wed Jan 31, 2024 3:24 pm

Re: Sending JSON Data in API Caller Body with CrossUI

Post by romain »

Thank you for providing this helpful code snippet. At the moment, I've developed my method using PUT and POST to interact with the API, and it's working exceptionally well for my current needs. I'll definitely keep your technique in mind for future forms that I'll be working on. It seems like a valuable approach to explore further. Thanks again for sharing!

Best regards,

Romain

   

// -----------------------------------------------------------------------------------------------
// JSON_query: Allows SQL-like operations on a JSON object of type SELECT
// -----------------------------------------------------------------------------------------------
function JSON_query(data, action, key_id) {
    // If the action starts with SELECT, we launch the SQL emulation function to 
    // manage JSON data like SQL tables with SELECT, WHERE, IN, AND, OR, LIKE, NULL, %, GROUP, ORDER....
    if (action.substr(0, 7) == "SELECT ") { 
        var a = SQL_query(action, data); 
        return a; 
    }

    // If the action starts with RENAME {id_el: 'id', intitule_el: 'caption'}, we can rename fields of the JSON object
    if (action.substr(0, 7) == "RENAME ") { 
        return fields_json_rename(action, data);  
    }

    // ------------------------------------------------------------------------------------------------------------------------------------------------
    // Handling PUT and POST actions
    // ------------------------------------------------------------------------------------------------------------------------------------------------
    // For example: received from the global CrossUI function named: JSON_query()
    //      -> a1) val (object): {page.XXXXXXXXXXX_form.getFormValues(true,null,true,true,true)}  // Only modified fields in the form using PUT method
    //      -> a2) val (object): {page.XXXXXXXXXXX_form.getFormValues(false,null,true,false,false)}// All fields in the form using POST method
    //      -> b) action (string): "PUT XXXXXXXXXXXX"                                              // Indicating here that we will use PUT method with the API on the endpoint "XXXXXXX"
    //      -> c) key_id (string): {page.id_el.getValue()}                                         // Primary key of the record to update (here: example with id_el for the elements table represented by XXXXX)
    // ------------------------------------------------------------------------------------------------------------------------------------------------
    if (action.startsWith("PUT ") || action.startsWith("POST ")) {
        // If the data object is empty, then nothing has been modified in the form, so we do nothing
        if (Object.keys(data).length == 0) {  
            return null; 
        }

        // Headers to be sent
        const headers = { 
            "accept": "application/json_v1",
            "authorization": "Token " + Token,
            "Accept-Language": "fr",
            "content-type": "application/json"  
        };

        // For example: elements that we'll call XXXXXXXXX here
        const endPoint = action.substring(action.indexOf(" ")).trim();

        // Method used by the API request: PUT or POST
        var method = action.substring(0, action.indexOf(" ")).trim();

        // URL to call for the API, composed of the endPoint (XXXXXXXXX), followed by / and the ID of the record to update if provided
        const url_endPoint = url_API_REST + endPoint + (key_id != null ? "/" + key_id : "");

        // Depending on the PUT method: transformation of modified fields received 
        // from the form by a format accepted by the API (with fieldname: {Value: "aaaaa"}....)
        if (method == "PUT") { 
            var JSON_form = JSON.stringify(JSON_API_format_form(data)); 
        }
        // For POST method: the original provided JSON format { name: value} by CrossUI is fine
        else if (method == "POST") { 
            var JSON_form = JSON.stringify(data); 
        }
        
        // Performs the AJAX request and returns the promise directly
        return ajax_request_api(method, url_endPoint, JSON_form, headers)
            .then(response => {
                // Sending a global message (Global broadcast to the CrossUI interface):
                // - {arg1}: the action to close the form: "close_form_XXXXXXXXXXXXX"
                // - {arg2}: the JSON returned by the API (before, after with the fields of the record before and after update)
                // - {arg3}: the object of modified fields in the form (argument "a)" received at the beginning of the JSON_query function in the variable "data")
                // - and on the CrossUI interface, 2 components listen to the message sent by broadcast -
                // => The "XXXXXXX_form" page which will close the form window
                // => The "XXXXXXX_window" page which will update the Treegrid row with the object of argument 3 (above)
                var object_response = JSON.parse(response);
                
                xui.broadcast("action", "close_form_" + endPoint, object_response, data, method);
            })
            .catch(error => {
                // Displaying an alert with the error
                xui.broadcast("action", "error_form_" + endPoint, error);
            });
    }

    // Returns a rejected promise if the action is not recognized to keep the function asynchronous
    return Promise.reject(new Error('Action not recognized.'));
}

   
romain
Posts: 30
Joined: Wed Jan 31, 2024 3:24 pm

Re: Sending JSON Data in API Caller Body with CrossUI

Post by romain »

in CrossUI, I go in Actions Editor
and for PUT API, I call Function : functions.JSON_query with :
val : {page.element_form.getFormValues(true, null , true, true ,true)}
action : PUT elements
key_id : {page.id_el.getValue()}

my script call API and then broadcast a global message for the next operations in crossUI to update Grid and more
romain
Posts: 30
Joined: Wed Jan 31, 2024 3:24 pm

Re: Sending JSON Data in API Caller Body with CrossUI

Post by romain »

Currently, I have been utilizing the API Caller for the GET method to retrieve a JSON with dozens of rows from a database, and it has been functioning admirably. I had configured my headers in the Web API caller interface as follows:

Code: Select all

    Authorization: Bearer Token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (hardcoded token for testing)
    Accept: application/json
    Content-Type: application/json; charset=utf-8
However, I now find myself needing to replace the hardcoded value for Authorization with a global variable (global.ARR.TOKEN), which will be an actual user token obtained from a cookie. The global variable is functioning correctly, and I am able to observe its value by using console.log in the Firefox Console.

Subsequently, I attempted to add the following into the Query Header:

Code: Select all

    Authorization: Bearer Token {global.ARR.TOKEN}
My issue arises from the fact that the variable is not being replaced by its real value when I invoke the API. In the Firefox Console, I can see the headers appearing like this:

Code: Select all

    Authorization: Bearer Token {global.ARR.TOKEN}
    Accept: application/json_v1
    Content-Type: application/json; charset=utf-8
Is there a solution to rectify this? Or do I need to utilize my own AJAX function to call the API and then send the JSON result to CrossUI?
romain
Posts: 30
Joined: Wed Jan 31, 2024 3:24 pm

Re: Sending JSON Data in API Caller Body with CrossUI

Post by romain »

For now, I have found a temporary solution.
My PHP login page for the application creates a cookie with a token once the user authentication is successful.

Then, in the interface created by CrossUI, before making an API invoke call, I call a global function with the following code:

Code: Select all

// setToken
// [this]: global
function(token/*String*/) {
    xui.APICaller._cache[0].getProperties().queryHeader.Authorization = "Bearer " + getCookie("token");
}
This allows me to dynamically modify the `queryHeader` with the correct token retrieved from the cookie created at the beginning of the application launch during the PHP login page.

I understand that I will need to add all the different APICallers created in CrossUI to my global function. If you have a better approach to accomplish this, I would greatly appreciate it.
support
Posts: 18
Joined: Thu Jan 25, 2024 4:58 pm

Re: Sending JSON Data in API Caller Body with CrossUI

Post by support »

There are 3 global hooks for APICaller:
APICaller-global-hooks.jpg
APICaller-global-hooks.jpg (91.63 KiB) Viewed 8243 times
The code will be in xuiconf.js:

Code: Select all

// [[Global Functions
xui.$cache.functions = {
    "$APICaller:beforeInvoke" : {
        "desc" : "set authToken or header for each APICaller",
        "params" : [
            {
                "id" : "callerPrf",
                "type" : "Object",
                "desc" : "the caller profile"
            }
        ],
        "actions" : [
            function(callerPrf){
                if( xui.debugMode && window.console ){
                    console.log( ">> [[API calling]]", callerPrf );
                }
                var ins = callerPrf.boxing(  ),
                    token = "",
                    custom_header = null;//{};
                if(token)ins.setOAuth2Token( token );
                if(custom_header)ins.setQueryHeader( custom_header );
            }
        ]
    },
    "$APICaller:beforeData" : {
        "desc" : "before data returns",
        "params" : [
            {
                "id" : "rspData",
                "type" : "Hash",
                "desc" : ""
            },
            {
                "id" : "req",
                "type" : "String",
                "desc" : ""
            },
            {
                "id" : "callerPrf",
                "type" : "Object",
                "desc" : ""
            }
        ],
        "actions" : [
            function(rspData, req, callerPrf){
                //console.log(rspData);
            }
         ]
    },
    "$APICaller:onError" : {
        "desc" : "error handler",
        "params" : [
            {
                "id" : "errMsg",
                "type" : "Hash",
                "desc" : ""
            },
            {
                "id" : "req",
                "type" : "String",
                "desc" : ""
            },
            {
                "id" : "callerPrf",
                "type" : "Object",
                "desc" : ""
            },
            {
                "id" : "status",
                "type" : "Number",
                "desc" : ""
            },
            {
                "id" : "statusText",
                "type" : "String",
                "desc" : ""
            },
            {
                "id" : "rspData",
                "type" : "Object",
                "desc" : ""
            }
        ],
        "actions" : [
            function(errMsg, req, callerPrf, status, statusText, rspData){
                //console.log(errMsg);
            }
        ]
    }
};
// ]]Global Functions
Post Reply