TWebWaitMessage animation?

I expected that the wheel would spin, but I've not been able to get that to happen when I call Show before starting a time-consuming action. I tried putting @AndrewSimard 's asm await sleep(x) end; trick in the OnShow event but the wheel stayed frozen. What am I missing?

I think that perhaps when you have a CPU-intensive bit of code, sometimes it starts executing before the rest of the UI has a chance to get sorted. And given JavaScript's single-threaded model, it doesn't quite get around to updating the UI until the CPU-intensive code has completed, so any kind of progress indicator might not even be shown.

I played around with this a bit to see what might help. In the code below, we've got a TWebButton component and a TWebWaitMessage component dropped on the form, with all of their defaults. In WebCreateForm, the only thing there is the definition of our JavaScript sleep function. In the onClick event for the button, the caption is updated with a "wait" and then a "ready" option.

Between the "wait" and "ready" options, we have the WebWaitMessage calls to show and hide, and two bits of code - the sleep call and some busy work, which takes around 3 seconds on my system - adjust up or down as you like - it is just a block of code intended to represent something CPU-intensive for a short time.

  1. With no sleep call, the WebWaitMessage animation never appears. And the button caption isn't updated either, until everything is done.
  2. If the sleep call is too small (say, less than 50), the WebWaitMessage animation never appears.
  3. if the sleep call is longer (say 3000) and no busy-work, the WebWaitMessage works fine.
  4. With the code as-is at sleep(50), the WebWaitMessage has time to make it onto the page and works fine while the busy work happens.

So while in general the JavaScript sleep() mechanism is largely discouraged as perhaps an outdated technique, it tends to be super useful in lots of unexpected situations.

unit Unit1;

interface

uses
  System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
  WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls,
  Vcl.Imaging.GIFImg;

type
  TForm1 = class(TWebForm)
    WebButton1: TWebButton;
    WebWaitMessage1: TWebWaitMessage;
    [async] procedure WebButton1Click(Sender: TObject);
    procedure WebFormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.WebFormCreate(Sender: TObject);
begin

  asm
    window.sleep = async function(msecs) {return new Promise((resolve) => setTimeout(resolve, msecs)); }
  end;

end;

procedure TForm1.WebButton1Click(Sender: TObject);
var
  i: Integer;
  d: double;
begin
  WebButton1.Tag := WebButton1.Tag + 1;
  WebButton1.Caption := IntToStr(WebButton1.Tag)+' Clicks - Wait';

  WebWaitMessage1.Show;

  asm await sleep(50); end;

  // busy work - takes only a couple of seconds
  d := 1;
  for i := 1 to 500000000 do
   d := d + sqrt(i*d)*sqrt(i*2*d)+sqrt(i*3*d)*sqrt(i*4*d);

  WebButton1.Caption := IntToStr(WebButton1.Tag)+' Clicks - Ready';

  WebWaitMessage1.Hide;
end;



end.

Thanks, Andrew! I set up a new PWA WebForm and pasted your code into it. When I ran it, the wheel appeared but stayed frozen. So I put the same code into a regular Web Core Form and it behaved as you describe. What is the secret to getting the wheel to spin in a PWA, hmm?

Deleted by author.

I followed the same steps starting with the PWA template and got the same results. I wouldn't have expected anything different, given that a PWA's differences tend to be elsewhere. Is there something else you did differently when creating the PWA?

Depending on what your "busy work" is, there may be other challenges. For example, there have been some projects where I've tinkered a bit with using WebAssembly - definitely busy work - and that blocks all kinds of things in unexpected ways when it is running.

Thanks. I tried to use the same steps and code. In fact, I pasted the PWA's code into the regular form. I'm seeing a similar result in my "real" PWA, which is what prompted this question in the first place. So when I saw this happen in your simple case I jumped to the conclusion that it's related to PWA v. normal WebForms. I'll repeat the experiment and try to discover what I did differently between the two.

Well...I started another PWA project from scratch and added only the two components and the two event handlers from Andrew's test project. The wheel shows, but doesn't spin. I attached here the complete project folder's zip. @AndrewSimard, does this work differently for you? (FWIW, my test browser is FF Developer Edition)
WebWaitPWADemo2.zip (40.9 KB)

Aha! It's the browser!! I just tried Edge and Chrome and both showed a spinning wheel.

Also, the regular edition of FF works the same as the Developer Edition: frozen wheel.

Indeed it is. Seems there are some variances in how browsers deal with these things. How unfortunate!

If you're interested in an alternative - using a spinning icon - this seems to work equally well in FireFox and Chrome, bypassing whatever is blocking the animated gif.

In this approach, an extra icon is tacked onto the button text to indicate that something is going on. Font Awesome is used in this case, so the "fa-spin" class can be added to any icon to get it to spin. Removing the "fa-spin" class of course generates a non-spinning icon. There are other classes as well as CSS that can be added to adjust the size, speed, direction, and so on, of the icon, spinning or otherwise. Here, a "spinner" icon is used.

procedure TForm1.WebButton1Click(Sender: TObject);
var
  i: Integer;
  d: double;
begin
  WebButton1.Tag := WebButton1.Tag + 1;
  WebButton1.Caption := IntToStr(WebButton1.Tag)+' Clicks - Wait - <i class="fa-solid fa-spinner fa-spin"></>';

//  WebWaitMessage1.Show;

  asm await sleep(3000); end;

  // busy work - takes only a couple of seconds
  d := 1;
  for i := 1 to 500000000 do
   d := d + sqrt(i*d)*sqrt(i*2*d)+sqrt(i*3*d)*sqrt(i*4*d);

  WebButton1.Caption := IntToStr(WebButton1.Tag)+' Clicks - Ready - <i class="fa-solid fa-check"></i>';

//  WebWaitMessage1.Hide;
end;

If you've not got Font Awesome in your project, you can use the Manage JavaScript Libraries for this, or just add a couple of lines to your Project1.html file. This uses Font Awesome 6 Free, but similar support has been available for prior versions as well. No doubt other font libraries support similar mechanisms.

    <!-- Font Awesome 6 Free -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.1/css/all.min.css" integrity="sha256-Z1K5uhUaJXA7Ll0XrZ/0JhX4lAtZFpT6jkKrEDT0drU=" crossorigin="anonymous">

There are other little things that stop working under heavy load. For example, in the above, there is a 3000ms wait before the heavy CPU stuff. During that period, mouseovers will change the color of the button. But once the heavy CPU stuff starts, the icon is still spinning, but no mouseover changes.

This is also why it is good to throw up a page-covering blocking like what WebWaitMessage does, so that other interactions are not possible. Maybe doing that but using the animating font instead of the animating GIF is what is needed.

progress

1 Like

Thanks, Andrew!

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.