PowerBuilder - Exchange rates example
The Exchange Rates demo has the following html:
<html>
<head>
<link rel="StyleSheet" href="exchangeRates.css" type="text/css">
<script language="JavaScript" src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js" type="text/javascript"></script>
</head>
<body>
<div class="card">
<h1>Exchange Rate</h1>
<form class="hiddencontainer">
<div class="row">
<div class="value" id="currencyRate"></div>
<label for="result">Result =</label>
<div id="result"></div>
<div id="resultCurrency"></div>
</div>
</form>
<div class="container">
<canvas id="graph" width="300" height="300"></canvas>
</div>
</div>
<script language="JavaScript" src="exchangeRates.js" type="text/javascript"></script>
</body>
</html>
as you can see there's a bit of javascript involved as well. Let us have a look at what is in the exchangeRates.js file.
var currencyChart;
function configureChart() {
const ctx = document.getElementById("graph").getContext("2d");
currencyChart = new Chart(ctx, {
type: "line",
data: {
labels: "",
datasets: []
}
})
}
function queryRates(firstValue,firstCurrency,secondCurrency) {
let typingTime = null;
const url = 'api.frankfurter.app';
clearTimeout(typingTime);
typingTime = setTimeout(() => {
async function fetchCurrency() {
let res = await fetch(`https://${ url }/latest?from=${ firstCurrency }`);
let dataRes = await res.json();
for (const [key, value] of Object.entries(dataRes.rates)) {
if (secondCurrency === key) {
document.querySelector(".value").innerText = value;
document.querySelector("#result").innerText = (firstValue * value).toFixed(2);
document.querySelector("#resultCurrency").innerText = secondCurrency;
}
}
}
fetchCurrency();
}, 1000)
return document.querySelector(".value").innerText;
}
function displayGraph(fromCHis,toCHis) {
const url = 'api.frankfurter.app';
async function fetchHistory() {
let resHistory = await fetch(`https://${ url }/2020-01-04..?from=${fromCHis}&to=${toCHis}`);
let dataResHistory = await resHistory.json();
const xlabels = [];
const ylabels = [];
for (const [key, value] of Object.entries(dataResHistory.rates)) {
xlabels.push(key);
ylabels.push(value);
}
const yScale = [];
ylabels.forEach(y => {
yScale.push(y[`${toCHis}`]);
})
currencyChart.data = {
labels: xlabels,
datasets: [{
label: 'Exchange rate',
data: yScale,
borderColor: '#0000ff',
}]
}
currencyChart.update();
}
fetchHistory();
}
configureChart();
OK.. that's a bit too much to explain line by line, but the gist is as follows.
The first thing it runs is at the bottom.
The configureChart function is called to configure the chart as is defined via the npm chart.min.js javascript include.
Then there's two functions.
1. The queryRates function that asks the api.frankfurter.app API for the current exchange rate for the value in firstValue and from currency firstCurrency in currency secondCurrency.
2. The displayRates function, also queries the api.frankfurter.app API, but this time for drawing the history chart.
As you can imagine, these API calls take time to execute and you don't want your application to hang if there's no timely response (if at all)
Now have a look at the PowerBuilder side of things.
The "Get Rates" button has the initial logic.
event clicked;
string ls_from
string ls_to
string ls_result
string ls_jscript
string ls_amount
string CrLf
string ls_decSep
decimal ld_amount
//any iv_params[]
integer li_error
amountedit.GetData(ld_amount)
if ld_amount <> 0 then
ls_from = fromcombo.text
ls_to = tocombo.text
if ls_from <> ls_to then
// no need
//io_document.SynchronousTimeOut = 6000 // set timeout to 6 seconds
rateedit.text = "Working..."
resultedit.text = "0.00"
CrLf = "~r~n"
// what is our current decimal separator ? Javascript wants a dot.
if pos(string(1/2), ".") > 0 then
ls_decSep = "."
else
ls_decSep = ","
end if
// seems like PowerBuilder has no variant support, thus.. manually adding the data as a default for the parameters on the function instead.
// not this:
// is_jScript = "(amount,curFrom,curTo) => {" + CrLf
// but this:
// is_jScript = "(amount = 2.4,curFrom = "EUR",curTo = "USD") => {" + CrLf
ls_amount = string(ld_amount)
if ls_decSep = "," then // replace comma with dot
int ll_Start
ll_Start = Pos(ls_amount, ls_decSep)
if ll_Start > 0 then
ls_amount = replace(ls_amount,ll_Start,1,".")
end if
end if
ls_jScript = "(amount = "+ls_amount+' ,curFrom = "'+ls_from+'",curTo = "'+ls_to+'") => {' + CrLf
ls_jScript = ls_jScript + " displayGraph(curFrom,curTo);" + CrLf
ls_jScript = ls_jScript + " let obj = document.getElementById('currencyRate');" + CrLf
ls_jScript = ls_jScript + " if (obj !== undefined) {" + CrLf
ls_jScript = ls_jScript + " obj.innerText = '-1'" + CrLf // -1 is initialized..
ls_jScript = ls_jScript + " }" + CrLf
ls_jScript = ls_jScript + " queryRates(amount,curFrom,curTo);" + CrLf
ls_jScript = ls_jScript + "}" + CrLf
ls_result = ""
li_error = io_Document.runanonymousfunctionsync(1,0,ls_jscript,REF ls_result)
if li_error <> 0 then
MessageBox("get rates", "The returned error = " + string(li_error))
else
// with the web all is asynchronous, we need to wait and check for the data to return
// we do that via a timer.
timer.start(1)
end if
else
MessageBox ("Error","That's a rate of 1, no need to ask that.")
end if
else
MessageBox ("Error","Enter an amount first")
end if
end event
First we get the amount value from the amountedit control and also the comboform values "from" and "to" for the currencies.
If you're not in the US then your decimal separate might be a comma (",") instead of a dot (".") and javascript expects a dot on numbers, so we need to take that into account.
With an anonymous function such as what we use here, we would normally pass the variables directly via a variant array like so:
Params[0] = ls_amount
Params[1] = ls_from
Params[2] = ls_to
ls_jScript = "(amount,curFrom,curTo) => {" + CrLf
Which then would pass ls_amount in javascript variable amount, ls_from in javascript variable curFrom and ls_to in javascript variable curTo.
But ... it seems that PowerBuilder does not support variant arrays.
So we came up with the workaround to use defaults from javascript so that you can keep on using variables instead of having to make your javascript harder to read.
eg. like so:
ls_jScript = "(amount = "+ls_amount+' ,curFrom = "'+ls_from+'",curTo = "'+ls_to+'") => {' + CrLf
OK.. this is the first call which calls the queryRates javascript function from earlier via the RunAnonymousFunctionSync function. This also sets the currencyRate html element to -1.
Note that if the above call runs correctly that it starts a timer "timer.start(1)"
The timer keeps on running until it returns the correct data and the currencyRate html element no longer contains -1.
The timer event code looks like:
event timer;
string ls_result
string ls_jscript
string ls_to
string CrLf
string ls_decSep
decimal ld_amount
decimal ld_rate
integer li_error
amountedit.GetData(ld_amount)
CrLf = "~r~n"
// what is our current decimal separator ? Javascript wants a dot.
if pos(string(1/2), ".") > 0 then
ls_decSep = "."
else
ls_decSep = ","
end if
ls_jScript = " () => {" + CrLf
ls_jScript = ls_jScript + " let rate = document.getElementById('currencyRate');" + CrLf
ls_jScript = ls_jScript + " if (rate !== undefined) {" + CrLf
ls_jScript = ls_jScript + " let data = rate.innerText;" + CrLf
ls_jScript = ls_jScript + " return data;" + CrLf
ls_jScript = ls_jScript + " } else {" + CrLf
ls_jScript = ls_jScript + " return '-2';" + CrLf // error "-2" == currency rate not found
ls_jScript = ls_jScript + " }" + CrLf
ls_jScript = ls_jScript + "}" + CrLf
li_error = io_Document.RunAnonymousFunctionSync( 3, 0, ls_jScript, REF ls_result )
if li_error <> 0 then
MessageBox("get amount", "The returned error = " + string(li_error))
else
if ls_result <> "-1" then
timer.stop()
if ls_decSep = "," then // replace comma with dot
int ll_Start
ll_Start = Pos(ls_result, ".")
if ll_Start > 0 then
ls_result = replace(ls_result,ll_Start,1,ls_decSep)
end if
end if
rateedit.text = ls_result
ld_rate = dec(ls_result)
ls_to = tocombo.text
resultedit.text = string(ld_rate * ld_amount) + " " + ls_to
end if
end if
end event
The javascript function check the currencyRate html element again and again for the value.
The moment it no longer contains -1, it has the actual currency Rate, which is returned in the -pass by reference- variable ls_result.
We might need to fix up the decimal separator again from a dot to a comma and with that we can now calculate the amount to display in the resultedit control.
Once the result is no longer -1, we stop the timer.
AntView - The MS Edge WebView2 ActiveX control Date last changed: 09/30/2024