Calling Stored Procedure from client-side code

When entering starting and ending dates in a new record from Add page, I would like to do a lookup on a separate Calendar table to list the total number of available dates in between the starting and ending dates, while filtering out the declared holidays.

I’m able to do this easily with a crafted SQL in a Stored Procedure. But is there a way to call the stored procedure from client side during user entry? Or can this be done in any other simpler way?

I have searched all earlier posts and it seems there should be a very easy way to do it!

Can you please advise how to proceed? I’m new to PHPMaker as well as to PHP and JS, but have a strong programming background in other formal languages.

Thank you for maintaining this wonderful forum!

SRanade

You may refer to the “Ajax by API and Client Scripts” sub-topic under the “Lookup Table” topic from PHPMaker Help menu for more info and example.

Even though the examples are intended for the field that uses “Lookup Table”, you should be able to implement the AJAX logic from it.

Thank you, that’s a great help!

What I need is closer to Example 2 of synchronous data.

I’ve looked at Ajax docs and other examples on this forum such as: https://discourse.hkvstore.com/t/how-to-call-stored-procedure-v2020/1221/1 but could not figure out the correct format required for the call.

Something like this:
var data = { CommandType: ‘StoredProcedure’, CommandText: ‘writelog_bare’ };
var res = ew.ajax(data);
where ‘writelog_bare’ is the name of a test stored procedure that takes no inputs but only writes to a log table.

But res comes as ‘undefined’ after the call.

Could you please guide me on the format for the Ajax call and how to pass variables?

The client side Ajax call will not call your stored procedure automatically. You need to write an API action on the server side to get the data (e.g. you use { CommandType: ‘StoredProcedure’, CommandText: ‘writelog_bare’ }) and build your SQL to execute the stored procedure and then return the result. To write the server side API action, you may refer to example 4 under “Ajax by API and Client Scripts”. To execute a stored procedure you can call, e.g.

ExecuteUpdate(“CALL writelog_bare()”); // Assume MySQL and the stored procedure does not return values

If the stored procedure returns record(s) or values, you may use ExecuteRow() or ExecuteRows() or ExecuteScalar(), see the topic Server Events and Client Scripts → Some Global Functions for more information.

(https://discourse.hkvstore.com/t/how-to-call-stored-procedure-v2020/1221/1 is for v2020, if you use v2021, it is out-dated.)

I’ve worked it out like this:

On Server Side:
$app->get(‘/getWeeklyDatesCount/{start_date, end_date, weekday_num, filter_holiday}’, function ($request, $response, $args) {
$start_date = $args[“start_date”] ?? null; // Get input values
$end_date = $args[“end_date”] ?? null;
$weekday_num = $args[“weekday_num”] ?? null;
$filter_holiday = $args[“filter_holiday”] ?? null;

if ($start_date !== null) {
$response = $response->write( ExecuteQuery(“CALL get_weekly_dates_count('”.$start_date.“‘,’”.$end_date.“‘,’”.$weekday_num.“‘,’”.$filter_holiday.“');”) ); // Output field value as string
}
return $response; // Return Psr\Http\Message\ResponseInterface object
});

On Client side, I put this into the Client Side Event of the field that should trigger the call:
{ // keys = event types, values = handler functions
“click”: function(e) {
// Your code
var $row = $(this).fields();
var res = ew.getApiUrl([“getWeeklyDatesCount”, $row[“slot_date_start”], $row[“slot_date_end”], $row[“day_of_week”], $row[“skip_holidays”]]);
if (res && res.success) {
alert( “Success!”);
var gotrow = res[table];
}

The code passes through but does not execute the call.

Can you please point out where this is wrong?

Second question: the example code in help file attaches client code to the on_change event in Add Page, but I find it better to insert in Client Side events. Is there any disadvantage/advantage to either?

Thank you for your guidance!

I realised that the Client side call is made only from .
So client code in Startup Script is:
$(“#x_skip_holidays”).change(function() {
var $row = $(this).fields();
$.get(ew.getApiUrl([“getWeeklyDatesCount”, $row[“slot_date_start”], $row[“slot_date_end”], $row[“day_of_week”], $row[“skip_holidays”]]), function(res) {
if (res && res.success) {
alert( “Success!”);
var gotrow = res[table];
$row[“repeat_count”].val(gotrow[“dates_count”]); // Set the result
}
});
});

Where “dates_count” is the name of the field returned by the Stored Function.

I think I’m very close to success. But some small format issue is preventing call from working. Can you please show me?

Thank you!

With some corrections:
$(“#x_timeslot_name”).change(function() {
var $row = $(this).fields();
$.get(ew.getApiUrl([“getWeeklyDatesCount”, $row[“slot_date_start”], $row[“slot_date_end”], $row[“day_of_week”], $row[“skip_holidays”]]), function(res) {
if (res) {
alert(“Success2!”);
$(“#x_repeat_count”).val(res);
}
else
alert( “Failure2!”);
});
});

It is giving failure always.

According to the Help file, please see the code in “Example 3” under “Server Events and Client Scripts” topic → “Client Scripts” → “Table-Specific → Add/Copy page” → “Startup Script” about how to access other fields by jQuery plugin .fields().

Thank you! This was very helpful!

Client side is now:
$(“input[data-field=‘x_timeslot_name’]”).change(function() {
$.get(ew.getApiUrl([“getWeeklyDatesCount”, $(this).fields(“slot_date_start”).value(), $(this).fields(“slot_date_end”).value(), $(this).fields(“day_of_week”).value(), $(this).fields(“skip_holidays”).value()]), function(res) {
if (res) {
alert(“Success2!”);
$(this).fields(“repeat_count”).value(res) // Set the result (manipulate it first if necessary) to the target field
}
else
alert( “Failure2!”);
});
});

It gives error message on browser Console: “jquery-3.5.1.min.js:2 GET /localhost/testproject/api/getWeeklyDatesCount/2020%2F09%2F14/2021%2F06%2F30/0/?csrf_name=csrf5f7e8b5f25e0a&csrf_value=3d70e2fd56cc0081ad537546029c309a 404 (Not Found)”

The values on the url look good, but is this how they are supposed to be passed? Or is the problem on server side?

Server side code is:
$app->get(‘/getWeeklyDatesCount/{start_date, end_date, weekday_num, filter_holiday}’, function ($request, $response, $args) {
$start_date = $args[“start_date”] ?? null; // Get input values
$end_date = $args[“end_date”] ?? null;
$weekday_num = $args[“weekday_num”] ?? null;
$filter_holiday = $args[“filter_holiday”] ?? null;

if ($start_date !== null) {
$response = $response->write( ExecuteQuery(“CALL get_weekly_dates_count('”.$start_date.“‘,’”.$end_date.“‘,’”.$weekday_num.“‘,’”.$filter_holiday.“');”) ); // Output field value as string
}
return $response; // Return Psr\Http\Message\ResponseInterface object
});

Btw, I’m using v2021.

SRanade wrote:

$app->get('/getWeeklyDatesCount/{start_date, end_date, weekday_num, filter_holiday}', function ($request, $response, $args) {

It should be ‘/getWeeklyDatesCount/{start_date}/{end_date}/{weekday_num}/{filter_holiday}’.

You should also convert the date format to that accepted by your stored procedure (if necessary).

You should also convert the date format to that accepted by your stored procedure (if necessary).

Hi, this was life-saving advice. The default locale has date separator as ‘/’ which converts to %2F but that is still interpreted as by Ajax as '/".

I found a way to convert date values to be independent of locale setting and fully compatible with SQL, like this:

$(“input[data-field=‘x_timeslot_name’]”).change(function() {
$.get(ew.getApiUrl([“getWeeklyDatesCount”, $(this).fields(“slot_date_start”).toJsDate().toISOString().split(‘T’)[0], $(this).fields(“slot_date_end”).toJsDate().toISOString().split(‘T’)[0], $(this).fields(“day_of_week”).value(), $(this).fields(“skip_holidays”).value()]), function(res) {
if (res) {
$(this).fields(“repeat_count”).value(res); // Set the result (manipulate it first if necessary) to the target field
}
});
});

Now the Ajax call goes through to Server side Api Action code:

$app->get(‘/getWeeklyDatesCount/{start_date}/{end_date}/{weekday_num}/{filter_holiday}’, function ($request, $response, $args) {
$start_date = $args[“start_date”] ?? null; // Get input values
$end_date = $args[“end_date”] ?? null;
$weekday_num = $args[“weekday_num”] ?? 0;
$filter_holiday = $args[“filter_holiday”] ?? 1;

console.log(“before check: $start_date $end_date $weekday_num $filter_holiday”); // this logs as: “log.DEBUG: before check: 2020-09-13 2021-06-29 0 1
if ($start_date !== null) {
console.log(“before CALL”); // this goes through
$response = $response->write( ExecuteScalar(“CALL get_weekly_dates_count_proc('”.$start_date.“‘,’”.$end_date.“‘,’”.$weekday_num.“‘,’”.$filter_holiday.“');”) ); // Output field value as string
console.log(“after CALL”); // this is never reached
}
return $response; // Return Psr\Http\Message\ResponseInterface object
});

I have tried with ExecuteQuery and ExecuteUpdate also. The call goes through when the stored Procedure has no return value. The call fails when we call a Stored Function that returns a value.

Stored Function is this:

DELIMITER $$
CREATE DEFINER=root@localhost FUNCTION get_weekly_dates_count(start_date DATE, end_date DATE, weekday_num INT(11), filter_holiday BOOLEAN) RETURNS int(11)
READS SQL DATA
BEGIN
DECLARE dates_count INT(11);
SET dates_count = 0;
SELECT COUNT(calendar_date) INTO dates_count FROM calendar WHERE (calendar_date BETWEEN start_date AND end_date) AND (day_of_week=weekday_num) AND ((NOT filter_holiday) or (NOT is_holiday));
RETURN dates_count;
END$$
DELIMITER ;

I’ve changed return value to VARCHAR also instead of INT, but it makes no difference. Looks like a very minor data type/interface issue now.

Please advise. Thank you!

SRanade wrote:

console.log(“before CALL”); // this goes through

After that line, try to output the values of all these variables:

  • $start_date
  • $end_date
  • $weekday_num
  • $filter_holiday

to make sure it contains a valid value for your Stored Procedure, and try again.

If your sp has return value then you have to do that using 2 query.
For example:
Execute(“CALL sp(input1,input2,etc,@out)”);
ExecuteScalar(“select @out”);

That is, you have to async instead of sync. IIRC, sync ajax in phpm can only pass 1 query.

Thank you for your guidance sangnandar and mobhar! I’ve managed to get Ajax calls and stored procedure calls fully working, but not functions so far. I will post my entire code with comments once I get this working!

Thank you again!

Using PHPMaker2021 I’ve been trying to:
A) Make Ajax call with multiple values and data types from client event to get data;
B) Call a Stored Procedure/Function on server side to get the data needed to return in the Ajax call.

I’ve succeeded in Ajax calls and calling Stored Procedures, but not in calling Stored Functions.

Below is a summary of my code and experiences, which I hope will help others who are new to this.

On Client Table Specific Startup script add:

$(“input[data-field=‘x_timeslot_name’]”).change(function() { //This format is critical: input[data-field=‘x_timeslot_name’]“)
$.get(ew.getApiUrl([“getWeeklyDatesCount”, $(this).fields(“slot_date_start”).toDate().format(‘YYYY-MM-DD’), $(this).fields(“slot_date_end”).toDate().format(‘YYYY-MM-DD’), $(this).fields(“day_of_week”).value(), $(this).fields(“skip_holidays”).value() ? 1 : 0]), function(res) {
if (res) { // ^^^^ date fields must be converted to toDate().format(‘YYYY-MM-DD’) to make MYSQL compatible and locale independent. Uses Moment object!
console.log(res); //<== use this for debugging res values and messages!
$(”#x_repeat_count").val(res); //Only this format works
//$(this).fields(“repeat_count”).value(res); // This format does not work!
}
});
});

I hope the variable names are self-evident. Comments are critical. Date field must be converted as above to string. If they contain “/” as in “2020/12/05” then the “/” will mess up the Ajax call. This method is independent of locale and timezone. Do not use toISOString() as it changes dates in correcting for time-zone!

The field skip_holidays is boolean. JS treats boolean False as Null. So you have to convert it to number using " ? 1 : 0" as above, otherwise Ajax call will fail. All fields must convert to string forms which are URL safe.

Use console.log(res); to get details of error messages on res. Press F12 to open console in Chrome. This can save you lots of trouble in debugging.

On the Server side in Global API Action insert the following:

$app->get(‘/getWeeklyDatesCount/{start_date}/{end_date}/{weekday_num}/{filter_holiday}’, function ($request, $response, $args) {
$start_date = $args[“start_date”] ?? null; // Get input values
$end_date = $args[“end_date”] ?? null;
$weekday_num = $args[“weekday_num”] ?? 0;
$filter_holiday = $args[“filter_holiday”] ?? 1;

//echo “Create slots from $start_date to $end_date, day_of_week: $weekday_num, filter_holiday $filter_holiday.
”; // Use this kind of code for debugging
//DO NOT USE console.log(“something”) anywhere on server-side code. While the message does get logged in text file, the $response code gets corrupted and always returns Server Error!

if ($start_date !== null) {
//must use ExecuteUpdate for all PROCEDURE calls. It is also critical that date fields must have single quotes around, though not needed for number/string fields
$response = $response->write( ExecuteUpdate(“CALL get_weekly_dates_count_proc(‘$start_date’,‘$end_date’,$weekday_num,$filter_holiday);”) );

//$response = $response->write( ExecuteScalar(“SELECT COUNT(*) FROM calendar WHERE (calendar_date BETWEEN ‘$start_date’ AND ‘$end_date’) AND (day_of_week=$weekday_num) AND ((NOT $filter_holiday) or (NOT is_holiday));”) ); // <== use this as alternative to calling Stored Function to get data from server

}
return $response; // Return Psr\Http\Message\ResponseInterface object
});

Format for multi-variable calls in Ajax is as above. Did not find any such example in Help pages or on the BB, until Arbei give me this code.

All data coming in from Ajax is string. Make sure to clean it using “?? default;” as above.

If your SELECT string is in double quotes, then PHP will automatically replace $variables by their actual string values. But for dates you HAVE to use single quotes around as they contain ‘-’ which can confuse SQL.

You must use ExecuteUpdate() for any calls to Stores PROCEDURES.

Stored FUNCTION calls require two calls as indicated by Sangnandar in previous reply. But I have not solved this yet.

So if you desperately need a function value returning, you can use a workaround by calling a PROCEDURE using ExecuteScalar or ExecuteRow or ExecuteRows. Sample code for ExecuteScalar is given above. In practice you can manage to do workarounds to avoid need for Functions in most cases.

ExecuteScalar returns a single value which you can use as is, both on server side and on the Ajax return on client side (see Client code as above).

ExecuteRows is more complicated. Sample below is from Server side (PHP) Row_Inserting event code. You can adapt for the Ajax event and make equivalent on Client side in JS. I cannot give sample here as I did not tryr it on Client side.

$rows = ExecuteRows(“SELECT calendar_date FROM calendar WHERE (DATE(calendar_date) BETWEEN DATE('”.date_format($slot_date_start,“Y-m-d”).“‘) AND DATE(’”.date_format($slot_date_end,“Y-m-d”).“')) AND (day_of_week=$day_of_week) AND ((NOT $skip_holidays) or (NOT is_holiday));”);

The date_format() was needed in this query because the format needed to be converted to match MySQL. This is not needed if you use the dates in the Ajax event as we already converted before sending in Ajax. But I left this code here as the format will be useful to anyone who has to make similar calls using dates on Server side query.

Then process the received rows like this:
foreach( $rows as $row ):
$class_date = date_create($row[“calendar_date”]);
// … etc
endforeach;

The above represents painful lessons learnt from many many hours of experimentation over several days and wide reading and search on the web. I hope it will save you the time and effort!

Special thanks to Mobhar and Arbei who gave critical guidance. Apologies that all the neat indentations I made are lost in this BulletinBoard settings, making the code difficult to read.

That’s all for now!