25
Views
9
Comments
Solved
Local variable in block changes but doesn't trigger OnRender
Application Type
Reactive
Platform Version
11.37.0 (Build 45622)


Hey everyone,


I made a block to use on several reactive apps, that allows the end user to upload a file, validate some stuff on the file name and trigger an event for the main screen to be able to execute the action associated with that upload (since the action differs from app to app).

Everything works great except for 1 "small" detail: The upload button is that special "Button On Loading" from Outsystems UI that has a boolean variable (called IsExecutingUpload) linked to it in order to prevent the end user from double clicking the button. The problem is......it's not preventing it !!! 

I'm changing the IsExecutingUpload to TRUE as soon as the button is clicked and I can see in debug that the variable changes to TRUE correctly, but if I Alt+Tab to the screen the variable is still showing FALSE (I also made an expression on the block to show the variable value). The following image is the code in the block's Upload action:


So, the OnRender event isn't getting triggered after that assign (the variable had FALSE before the assign and has TRUE after it). Is this behaviour expected or a bug? 

Btw, I managed to find a way to make this work doing some "cheating", but I'd like to know if my original way was supposed to work or not.

For those who are curious, "the cheating" was to execute a dummy DataAction that I created on the block and putting the final code on it's after fetch event like this:


regards

2026-01-28 16-57-48
Mihai Melencu
Champion
Solution

Hi,

Thanks for the tag Dorine.

Think of your Client Action like you telling someone to do things step by step without stopping: “turn loading on, start upload, finish”. The browser listens to everything in one go and only updates the screen after you’re done talking, so the button never gets a chance to look disabled in time.

When you use async JavaScript in OutSystems, it’s like you tell a helper: “pause here and come back to me when you’re ready”. That helper is basically a Promise. $resolve() means “ok, I’m back, you can continue now”, and $reject() means “something went wrong, go to the error flow”.

In your case, when you call $resolve() later (with something like setTimeout), you’re giving the browser a small break to update the screen before continuing the upload. So the flow becomes: turn loading on > pause  > screen updates > continue. Without that pause, everything runs in one go and the UI never updates in time.

OutSystems has two predefined functions, but internally it behaves like:

let promise = new Promise(function(resolve, reject) { // code) });

__________

@Gonçalo Almeida - you already got the explanation why your intiial implementation didn't behave as you expected and why your workaround did, so I won't get into that.

What you can do, to avoid calling the data action, is to use an async JS node (resolve in your case), your flow should look something like:

When you debug you will see, after the JS node is executed, and before the Event is triggered, the button will be updated.

The JS I used is:

  • setTimeout(function () {
  •     $resolve();
  • }, 0);

Now, your use-case is pretty simple, you don't need any validation, but for more complex scenarios if you want to implement something in the future or if this use-case becomes more complex, you can check the article Dorine shared and also can take a look at this one, which has a lot of examples and great explanations: 

__

For resetting the state of the button when the upload if finished I would recommend to use an input like that changes when the parent finishes, and reset the block’s local state in OnParametersChanged. 

AsyncSample.oml
2019-11-11 17-10-24
Manish Jawla
 
MVP

Hi @Gonçalo Almeida ,

Changing a local variable inside a block won’t trigger OnRender in OutSystems Reactive, because it doesn’t cause the screen or block to re-render.

OnRender only runs when there is an actual re-render, like when an input parameter changes or data is refreshed. Local variables are just internal changes, so they don’t trigger this lifecycle event.

So even if you see the UI update, OnRender still won’t run unless a full re-render happens.

Regards,

Manish Jawla

2018-11-09 09-14-06
Gonçalo Almeida

Thank you for your answer.


My question is: it should re-render because the "Button On Loading" from OutsystemsUI is bound to the local variable that I'm changing and that button should show a spinning wheel if the variable is TRUE, so this should cause a re-render because it affects a widget in the block. 

Am I thinking incorrectly?

2024-12-02 12-15-17
Aravind EONE

Yes, Correct. Since it is bound to a widget it should trigger the on render event conceptually.

2024-12-02 12-15-17
Aravind EONE

When I test this on a screen level, I can see the button and the onRender is working fine.

2018-11-09 09-14-06
Gonçalo Almeida

Yes, I think this only happens because I'm using all of this inside a web block.

It works as expected if you don't use a web block.

2024-12-02 12-15-17
Aravind EONE

Yeah, probably you can create a placeholder in your block for this loader button. So it can be used on a screen level.

2021-09-06 15-09-53
Dorine Boudry
 
MVP

Hi,

So, the OnRender event isn't getting triggered after that assign (the variable had FALSE before the assign and has TRUE after it). Is this behaviour expected or a bug? 

That's by design, and doesn't have anything to do with it being in a block or not.  During the execution of a client action, you change values of variables, nothing triggers a render yet, until the client action ends.  

I'm curious what you people exactly do when you say 'It works on screen'

You triggering the "ExecuteUpload" event is just a call of the attached handler in the parent screen, your action "UploadOnClick" will wait for that to end before continuing.  So, you can't do this [set isLoading + trigger Event] to hope that, during the lengthy execution of the handler logic in the screen, a spinner will be visible on the button.

You want to force the end of the UploadOnClick, to force a render.  That trick with the data action is exactly that.  

Alternatively, to avoid going to the server, you could simply have 2 actions, OnUploadClick and TriggerUploadEvent.  The OnUploadClick sets the IsLoading to true and has a javascript node with as single statement after which it will immediately end and cause a render.

setTimeout($actions.TriggerUploadEvent,0);

Then the TriggerUploadEvent fires the trigger, and after that updates the IsLoading to False again.


Note :

All of the above changes as soon as a server call is involved in a client action.  At that point, the call uses the promise mechanism, so the calling action ends with a render immediately.  You can also use the promise mechanisme in your code (regardless of server calls)

This is more advanced javascript I haven't managed to wrap my head around yet.  I often revisit, the explanation is here, but I always fail to get it to work, and then just give up and use some dirty trick like the ones above.  So if anybody gets that stuff, a demo oml would be greatly appreciated.  Maybe @Mihai Melencu ? You seem to have a deep understanding of Javascript.

Dorine

2026-01-28 16-57-48
Mihai Melencu
Champion
Solution

Hi,

Thanks for the tag Dorine.

Think of your Client Action like you telling someone to do things step by step without stopping: “turn loading on, start upload, finish”. The browser listens to everything in one go and only updates the screen after you’re done talking, so the button never gets a chance to look disabled in time.

When you use async JavaScript in OutSystems, it’s like you tell a helper: “pause here and come back to me when you’re ready”. That helper is basically a Promise. $resolve() means “ok, I’m back, you can continue now”, and $reject() means “something went wrong, go to the error flow”.

In your case, when you call $resolve() later (with something like setTimeout), you’re giving the browser a small break to update the screen before continuing the upload. So the flow becomes: turn loading on > pause  > screen updates > continue. Without that pause, everything runs in one go and the UI never updates in time.

OutSystems has two predefined functions, but internally it behaves like:

let promise = new Promise(function(resolve, reject) { // code) });

__________

@Gonçalo Almeida - you already got the explanation why your intiial implementation didn't behave as you expected and why your workaround did, so I won't get into that.

What you can do, to avoid calling the data action, is to use an async JS node (resolve in your case), your flow should look something like:

When you debug you will see, after the JS node is executed, and before the Event is triggered, the button will be updated.

The JS I used is:

  • setTimeout(function () {
  •     $resolve();
  • }, 0);

Now, your use-case is pretty simple, you don't need any validation, but for more complex scenarios if you want to implement something in the future or if this use-case becomes more complex, you can check the article Dorine shared and also can take a look at this one, which has a lot of examples and great explanations: 

__

For resetting the state of the button when the upload if finished I would recommend to use an input like that changes when the parent finishes, and reset the block’s local state in OnParametersChanged. 

AsyncSample.oml
2018-11-09 09-14-06
Gonçalo Almeida

Thank you @Dorine Boudry  and @Mihai Melencu for your answers.

To answer your question Dorine,

"I'm curious what you people exactly do when you say 'It works on screen' "
It works because when I use this with no blocks there's no need to use events and therefore, I call the upload server action imediatelly after the assign thus rendering the button correctly.

Community GuidelinesBe kind and respectful, give credit to the original source of content, and search for duplicates before posting.