The problem
Suppose you have a IWButton in a form and want to trigger an async (AJAX) request that will take a long time to return. For instance, you will create a complex report, or generate a complex file to be downloaded later. You want your user to click the button and have some kind of feedback that your application is working, yes? At the same time you don't want your user to keep clicking all over the place... The best option, IMO, is tho show the IWLocker (the IW AJAX "loading animation") during the request. I will show you three different techniques to do that. Very similar but you may prefer one over the other.First solution
For this solution, I will create a JavaScript function that will be called by your button directly, without any OnAsyncClick event handler. First, you have to create the JavaScript function. Let's say:function myAjaxFunc() { var myData = "Nicole Scherzinger,Scarlett Johansson,Jennifer Lawrence,Doutzen Kroes,Charlize Theron"; ShowBusy(true); executeAjaxEvent("&data="+myData, null,"IWFORM1.DoMyAjaxFunc", false, null, false); return true; }This is a simple js function: There is a variable myData that holds the data that I want to send to the server, in this case, a comma separated list of names, but it can be whatever you want. Then I call ShowBusy, and after that I call the real AJAX request, executeAjaxEvent.
Note that to show the IWLocker, I'm calling ShowBusy function. ShowBusy is declared in IWPreScript.js, a JavaScript library that is available to all your IWForms. ShowBusy expects a single boolean parameter. If you pass true the IWLocker will be shown, and if you pass false the IWLocker will be hidden. Also note that executeAjaxEvent will call IWFORM1.DoMyAjaxFunc method of your form. This method must be registered so IW core can call it. To register that method, first you have to create the method DoMyAjaxFunc in your IWForm1:
const CDATATag = '< ! [CDATA[%s] ] >'; // spaces added so blogger won't mess the CDATA string procedure TIWForm1.DoMyAjaxFunc(EventParams: TStringList); var ResponseFunc: string; sl: TStrings; s: string; begin sl := TStringList.Create; try sl.StrictDelimiter := True; sl.CommaText := EventParams.Values['data']; s := sl.Strings[Random(sl.Count)]; finally sl.Free; end; Sleep(5000); // simulate a long operation IWLabel1.Caption := 'The hot chick chosen is: ' + s; ResponseFunc := Format(CDATATag, ['ShowBusy(false);']); WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc); end;The code above shows the DoMyAjaxFunc. It retrieves the "data" parameter sent from myAjaxFunc() and simulates a long operation (in this case, 5 seconds). When this method finishes, it sends ShowBusy(false) to the browser using WebApplication.CallBackResponse.AddJavaScriptToExecute method.
Then we must register DoMyAjaxFunc callback, using:
WebApplication.RegisterCallBack(UpperCase(Self.Name) + '.DoMyAjaxFunc', DoMyAjaxFunc); // Self.Name -> IWForm1Finally you must call the JavaScript function myAjaxFunc from some JavaScript event directly, let's say in the onClick button event handler:
procedure TIWForm1.IWAppFormCreate(Sender: TObject); begin IWButton1.ScriptEvents.HookEvent('onClick', 'myAjaxFunc();'); end;I hooked myAjaxFunc() to the onClick event handler of IWButton1 in runtime. It can be done in design time, using Object Inspector as well.
That's it! If you run your application and click on the IWButton1, you will see that IWLocker will become visible for 5 seconds (the time that DoMyAjaxFunc() method takes to run) and then will be hidden again.
Second solution
The second solution is very similar to the first, but instead of calling your myAjaxFunc() directly from your onClick event handler, you can use IW async events to call it, in this case, IWButton2 (another button in IWForm1) onAsyncClick event:const CDATATag = '< ! [CDATA[%s] ] >'; // spaces added so blogger won't mess the CDATA string procedure TIWForm1.IWButton2AsyncClick(Sender: TObject; EventParams: TStringList); var ResponseFunc: string; begin ResponseFunc := Format(CDATATag, ['ShowBusy(true);myAjaxFunc();']); WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc); end;Inside IWButton2 OnAsyncClick event handler I add JavaScript to execute, again using AddJavaScriptToExecute method. The JavaScript code that will be executed is a call to ShowBusy (showing the IWLocker) and then a call to myAjaxFunc();.
Third solution
The third solution is a little different from the others. It uses the IWButton3 OnAsyncClick twice. Let's see how:procedure TIWForm1.IWButton3AsyncClick(Sender: TObject; EventParams: TStringList); var ResponseFunc: string; FirstCall: boolean; begin FirstCall := (EventParams.Values['SecondCall'] <> 'true'); if FirstCall then // this code will run in the first IWButton3AsyncClick call begin ResponseFunc := Format(CDATATag, ['ShowBusy(true);executeAjaxEvent("&SecondCall=true", null,"' + IWButton3.HTMLName + '.DoOnAsyncClick", false, null, false);']); end else begin // this code will run in the second IWButton3AsyncClick call Sleep(5000); // simulate a long operation ResponseFunc := Format(CDATATag, ['ShowBusy(false);']); IWLabel1.Caption := 'Result returned'; end; WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc); end;When you click the IWButton3, the OnAsyncClick event is fired. You then search for some parameter, in this case "SecondCall" in the EventParams list. If SecondCall is not there, then this is the first OnAsyncClick call. In the first call we add some JavaScript code to be executed. This code shows the IWLocker (calling ShowBusy(true)), and then calls the OnAsyncClick event again, this time passing the SecondCall parameter to the method.
When the event OnAsyncClick is fired the second time, there will be a SecondCall parameter in EventParams list and then we know that this time we must do the "real" work. When the method finishes, it will then call ShowBusy(false); hiding the IWLocker again.
Conclusions
The three techniques have the same effect: Show IWLocker during a long AJAX async request. Using the first method, your application will use a single AJAX request to your server, but you have to write some JavaScript code. Using the third method you won't have to write a single line of JavaScript but there are two AJAX requests involved. If you are not comfortable writing JavaScript you may prefer this solution. The second has the worst of both worlds: Two AJAX requests involved and some JavaScript coding... ;-)Download the sample project
You can download a complete project showing all the three methods here.Enjoy!
2 comments:
Did you release the "Doing nice things with Intraweb + jQuery, JQGrid" source code ? Couldn't find it anywhere on your site.
Hello, I am not able to retrieve the file via download link. Could you share your code via attachment news group in the embarcadero forums.
Post a Comment