Contents - Index - Top


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