[Howto] Wait-indicator in long processes without timer or threads

Hi,

this is my second tutorial for XojoWE.

The problem:
You have a webapp where some processes takes a long time. Most of us want to display the users, that there something happen in the background. All operations are calculated by the webserver and the results are send back to the user until completing ALL calculations. In the meantime the app is not really blocked there is no indicator that the app do something.

One solution is to put all the time-consuming operations in the run-event of a timer. In the main-thread you can display a wait-spinner or a modal dialog or so.
But, me personally is this more a “workaround” and not a solution. If you have several time-taking functions, you’ll need a lot of timer or your code will become messy.

My solution:
My solution is to manipulate the DOM of your app with Javascript. (Xojo-Staff will remind, that this is not officially supported - meh).

What do you need:

1. Two JavaScript-Functions:
Place this Code into you App.HTMLHeader-Property:

<script>
function show_indicator() {
    var x = document.getElementsByClassName("progress_hidden");
    for (var i = 0; i < x.length; i++) {
       x[i].className = 'progress_visible';
    }
}

function remove_indicator() {
    var y = document.getElementsByClassName("progress_visible");
    for (var i = 0; i < y.length; i++) {
       y[i].className = 'progress_hidden';
    }
}
</script>

2. Two CSS-Classes:
Also place this code in your App.HTMLHeader-Property:

<style>
.progress_visible{
  width: 100% !important;
  height: 100% !important;
  z-index: 100000 !important;
  background-color: rgba(221, 221, 221, 0.4);
  left: 0px !important;
  top: 0px !important;
}
.progress_hidden{
	display: none !important;
}
<style>

3. A Xojo-Webstyle:
Create in the XojoIDE a Webstyle and name it “progress_hidden”

4. A ContainerControl with a Progresswheel:
Create a ContainerControl, place a ProgressWheel inside and add the CC to your main Page.
Apply the Webstyle “progress_hidden” to this CC.

5. Two Xojo-Funtions:
Add the following Functions to your App:

Sub RegisterWaitindicator(cntrl as WebControl)
  WebPage1.ExecuteJavaScript("document.getElementById(""" + cntrl.ControlID + """).onclick = show_indicator")
End Sub
Sub RemoveWaitIndicator()
    WebPage1.ExecuteJavaScript("remove_indicator();")
End Sub

How to use:
Select the button which starts a time-consuming operation. Add the following code into the Shown-Event:

app.RegisterWaitindicator(me)

And on the end of the time-consuming function you’ll have to add the following:

App.RemoveWaitIndicator()

[h]That’s it![/h]

How it works:
When the Shown-Event of the webbuttons raises, the Xojo-function “RegisterWaitindicator()” is called. Inside this Function a little Javascript adds an own, non xojo-driven, click-event to the webbutton.

Now, if you click the webbutton, this event will be fired and the custom JS-Method in your App.HTMLHeader is called. This JS-Method applies another style to your ContainerControls, which displays it with the Progresswheel.

After completing the time-consuming method the method “RemoveWaitIndicator” calls the JS-Funktion which make the CC invisible again.

All this is done in the user-browser, so xojo don’t crash.

Here is a test project:
https://www.dropbox.com/s/kzcdk78srx15hmj/WaitIndicatorProject.xojo_binary_project?dl=0

Bye
Lars

1 Like

Thank you Lars for your tutorials and the time you spent to give this informations and examples to the community.
I liked very much your tutorial related to CSS.

As a comment to this example an alternative way is to use WebThreads.
Using WebThreads you have a connection to the starting session and, if needed, you can push data directly to the page from the thread itself.

Best regards.

[quote=201826:@Lars Lehmann]Here is a test project:
https://www.dropbox.com/s/kzcdk78srx15hmj/WaitIndicatorProject.xojo_binary_project?dl=0

[/quote]

The project does something bizarre in 2015R2.2 under Yosemite 10.10.4 : when I hit run, it seems to compile fine, but then instead of launching the browser, it comes right back to edition mode.

I’ve updated the testproject. I have a custom debug-folder. Now it should work!

[quote=201833:@Maurizio Rossi]As a comment to this example an alternative way is to use WebThreads.
Using WebThreads you have a connection to the starting session and, if needed, you can push data directly to the page from the thread itself.[/quote]
Sure, but I did not found a satisfiying solution. I searched for a solution which I can simple implement after finishing one of my apps. With threads I had do rewrite a lot of code.

With this solution I only have to add some lines of code.

It still did the same, but I found where the issue was. I changed the debug port in build setting to 8080, and it runs fine.

I’ll change this! Thanks!

Edit: Done

Addition:
If you add the follwing code to the styles between the <style></style>-Tag, the Wait-CC will fade in smoother:

[code].progress_visible {
-webkit-animation: move_eye_in 0.1s linear 0s alternate;
-moz-animation: move_eye_in 0.1s linear 0s alternate;
-o-animation: move_eye_in 0.1s linear 0s alternate;
animation: move_eye_in 0.1s linear 0s alternate;
}

@-webkit-keyframes move_eye_in { from { opacity: 0; } to { opacity: 1;} }
@-moz-keyframes move_eye_in { from { opacity: 0; } to { opacity: 1;} }
@-o-keyframes move_eye_in { from { opacity: 0; } to { opacity: 1;} }
@keyframes move_eye_in { from { opacity: 0; } to { opacity: 1;} }
[/code]

…once again: Good work Lars! Thank you!

I do something similar for long processes, but you don’t have to manipulate the DOM.
All I need to call is DoProcesses and pass an array of as many methods as I want:

DoProcesses(AddressOf CBVisualization1.Reload, addressof showChart)

DoProcessesMethod:

Sub DoProcesses(ParamArray Processes() as WDProcessing.BlankDelegate) dim wd as new WDProcessing wd.Processes = Processes wd.Show End Sub

The WDProcessing WebDialog nothing but a centered WebSpinner for the UI.
It has a blankDelegate:

Delegate Sub BlankDelegate()
I Property called processes:

Processes() As BlankDelegate

And a shown event:

for i as integer = 0 to Processes.Ubound try Processes(i).invoke Catch end next

1 Like

Oha, I have had to know this earlier :smiley:

Fine, one Howto, two solutions

A couple other things to note:

Remember to call Self.Close at the end of the shown event (I missed it in my example)
You’ll probably want to handle exceptions a little differently then gobbling them up.
You can create a “Session.Pause” property on the Session object just incase the user gets “click-happy” these extra click events get ignored and then I call turn off session.pause when I close the WebDialog.

Hi Brock,
please, could you post a little working project ?

Thanks

[quote=202272:@luciano monti]Hi Brock,
please, could you post a little working project ?

Thanks[/quote]

Sure!
https://www.dropbox.com/s/81srs6cuigguyrt/WaitIndicatorExample.xojo_binary_project?dl=0

1 Like

Great job Brock.

Thanks a lot!