I'm trying to get a "Loading..." popup to display when I do a request to the server on mobile. To this end, I have a popup widget on my main layout with the loading text and icon.

Now, I tried just having an input parameter for my layout from every single screen of "IsLoading", and updating the popup when it has changed, however the issue arises when an exception occurs after showing the popup. The exception goes through the global error handler, which shows a feedback message, but leaves the loading popup up. My global error handler can't access that screen's IsLoading variable, so I can't close the popup. I'd need to implement my global error handler's logic in each and every screen action. That coupled with the necessity of having an isLoading variable on every screen makes this a very messy approach that I'd like to avoid.

So next idea used two client actions, IsLoading and SetLoadingState. Basically, SetLoadingState runs Javascript and sets the value of window.localStorage["isLoading"] to whatever is passed to it, and then IsLoading returns that value. Problem with this is that Outsystems caches the data of the IsLoading function call, and skips over re-evaluating the value in Model.js's getCachedValue, even though it gets to the point where it is about to re-run IsLoading:

BaseViewModel.prototype.getCachedValue = function (key, getValue) {
    var deferredArgs = [];
    for (var _i = 2; _i < arguments.length; _i++) {
        deferredArgs[_i - 2] = arguments[_i];
    }
    var expr = this.cachedValues[key];
    var valueArgs = deferredArgs.map(function (deferred, i) {
        try {
            return deferred();
        }
        catch (ex) {
            return BaseViewModel.InvalidValue;
        }
    });
    var useCachedValue = expr !== undefined
        && deferredArgs.length === expr.args.length
        && valueArgs.every(function (e, i) { return e === expr.args[i]; });
    if (!useCachedValue) {
        expr = { args: valueArgs, value: getValue() };
        this.cachedValues[key] = expr;
    }
    return expr.value;
};

So my question is this: Does anyone have a good way to store a local variable that my Layout will recognize as changed from anywhere in the app? Or a way to flush the model's cached values from a client action? Or alternatively, is there Javascript I can use to hide my loading popup? I'd really like to avoid using my first solution with the tens of duplicate exception handlers and local variables.

Hi Jason,

Perhaps I did not understand the issue properly, but I think you are thinking way too difficult. If you want to fetch data from the server, you can use the 'IsDataFetched' property. If you enclose the content in an IF statement, with the statement being 'IsDataFetched' of that aggregate, you can show the 'Loading...' popup.

Would that solve the issue? Or are you requesting something else from the server? Perhaps there are similar workarounds.

Regards,

Sam

Another option could be to create a local database entity with an 'IsLoading' attribute, which you fetch in the layout block of the screen. You may set the value of 'IsLoading' in an client action if the server request has returned the expected result.

Regards,

Sam.

Sam Rijkers wrote:

Hi Jason,

Perhaps I did not understand the issue properly, but I think you are thinking way too difficult. If you want to fetch data from the server, you can use the 'IsDataFetched' property. If you enclose the content in an IF statement, with the statement being 'IsDataFetched' of that aggregate, you can show the 'Loading...' popup.

Would that solve the issue? Or are you requesting something else from the server? Perhaps there are similar workarounds.

Regards,

Sam

So I'm not requesting data from the server to display on the screen, necessarily. Simple example, I've got a button that assigns an order to a user, then redirects the user to the order page. After the user clicks the button, but before the page loads, I need to display the loading popup (which is located in the layout). The request to the server is done by a server action within the OnClick screen action, so there's no IsDataFetched property to work with.

Not that it would really matter anyways, because the loading popup is down at the layout level, rather than the screen I'm currently working on.

Sam Rijkers wrote:

Another option could be to create a local database entity with an 'IsLoading' attribute, which you fetch in the layout block of the screen. You may set the value of 'IsLoading' in an client action if the server request has returned the expected result.

Regards,

Sam.

How would I get the layout to recognize that that value has changed?

I Jason,

You have some alternatives, but why should the popup be on the layout? perhaps a block like menuIcon (Common Flow) should do the trick.
- You have native plugin to show a native loader - You'll need to generate the app and install again
- Before you call the server action you can set a variable (i.e isOpen) to true and after the server action you assign the variable to false 

  • If the popup is on the screen, you assigned directly to the input variable that controls the popup. 
  • If the popup is inside a block, you'll need to add an handler for the OnparametersChanged of the block.

Hope it helps

Henrique Batista wrote:

I Jason,

You have some alternatives, but why should the popup be on the layout? perhaps a block like menuIcon (Common Flow) should do the trick.
- You have native plugin to show a native loader - You'll need to generate the app and install again

This is not an issue, I haven't even generated the app yet in the first place. What's the native loader you're refering to? Is that forge plugin?


- Before you call the server action you can set a variable (i.e isOpen) to true and after the server action you assign the variable to false 


  • If the popup is on the screen, you assigned directly to the input variable that controls the popup. 
  • If the popup is inside a block, you'll need to add an handler for the OnparametersChanged of the block.

Hope it helps

This isn't an really improvement over the initial solution I specified in the second paragraph of my initial post. If anything, it gets more complicated because now you've got a popup widget for each and every screen to manage.

If there's no other way, I'm prepared to take that original solution, however a simple solution with what amounts to the mobile equivalent of an Outsystems session variable (which I can prompt the layout to refresh) would be much cleaner in terms of simplicity.

Jason,

But the layout is a block, it can have an input parameter and a handler for the onParametersChanged event, that will open/close your popup. Isn't this what you were looking for?


Henrique Batista wrote:

Jason,

But the layout is a block, it can have an input parameter and a handler for the onParametersChanged event, that will open/close your popup. Isn't this what you were looking for?


No. As I stated in my original post, doing this would prohibit me from using a global exception handler to hide the loading popup if the request fails. Instead I would need to implement the same exception logic for each and every screen action, which would be a nightmare to maintain.


Sorry, only now I see the problem.

You're right, I'll do some research and get back if I find something useful.

Solution

Hi Jason,

I'm not sure if it's the better solution, but let's bring it up to discussion. 

The idea behind it is, since Outsystems mobile is an SPA, I tried with an window object and it seems to work. It's basically your second paragraph but with an js object 

I created an object on the layout ready event, something like the following:

window.loadingPopup = {
    show: function () {
        $actions.ShowPopup();
    },  
   
    hide: function () {
        $actions.HidePopup();
    }
}


Where both actions are defined on the layout and the name is self explanatory.

I then created 2 client actions (open and close):


On the exception handler I see if the object exists, and call this client action. It can have extra logic to be more robust, but it seems to do the trick.

Let me know your thoughts.

Cheers


Solution

Great solution, thanks Henrique.

Sorry for misreading your first post!

Happy that worked out!