Monday, March 12, 2012

Show "loading animation" during long Intraweb AJAX requests

Yesterday I was talking to another Intraweb developer and I've created a demo application to show him how I lock the screen, showing the loading AJAX animation (IWLocker), during an AJAX request that takes long time to return. After that, I've improved my demo using 3 different techniques do do that. In this post I will show you all the 3 techniques.

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";
  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:
  CDATATag = '< ! [CDATA[%s] ] >';  // spaces added so blogger won't mess the CDATA string

procedure TIWForm1.DoMyAjaxFunc(EventParams: TStringList);
  ResponseFunc: string;
  sl: TStrings;
  s: string;
  sl := TStringList.Create;
    sl.StrictDelimiter := True;
    sl.CommaText := EventParams.Values['data'];
    s := sl.Strings[Random(sl.Count)];
  Sleep(5000);  // simulate a long operation
  IWLabel1.Caption := 'The hot chick chosen is: ' + s;
  ResponseFunc := Format(CDATATag, ['ShowBusy(false);']);
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 -> IWForm1
Finally 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);
  IWButton1.ScriptEvents.HookEvent('onClick', 'myAjaxFunc();');
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:
  CDATATag = '< ! [CDATA[%s] ] >';  // spaces added so blogger won't mess the CDATA string

procedure TIWForm1.IWButton2AsyncClick(Sender: TObject; EventParams: TStringList);
  ResponseFunc: string;
  ResponseFunc := Format(CDATATag, ['ShowBusy(true);myAjaxFunc();']);
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);
  ResponseFunc: string;
  FirstCall: boolean;
  FirstCall := (EventParams.Values['SecondCall'] <> 'true');
  if FirstCall then  // this code will run in the first IWButton3AsyncClick call
    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';
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.


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.


Wednesday, March 7, 2012

Crossbrowser, multi-line hints in Intraweb

Yesterday I saw a post in Embarcadero forums asking how to create multi-line hints in a IWGrid, inside an Intraweb application.
Well, this doesn't have a simple answer because IWGrid cells are TD HTML tags, and the hint is the title attribute. There is no reliable way to make the title attribute break lines across browsers. It is doable in IE but not in Firefox, for instance.
I will show you a technique that can be used in your Intraweb application to customize the hints. I will use a small javascript library in this example, but other library can be used as well. I'm using one of the simplest solutions I've found, from Craige Erskine, old qTip. The advantage of this release of qTip is that it is simple, compact and doesn't require another JavaScript library (jQuery, etc). It is composed of two files only: qTip.js and qTip.css. I have modified this release of qTip a little because it was replacing the windows.onload event with its own handler - not a good thing to do in JavaScript...

Include qTip files in your IWForm

Each form rendered that will use qTip hints need to include qTip files (qTip.js and qTip.css). This is done using one of my utility classes TIWContextHelper (file included in the download), in the FormRender event:

procedure TMyIWForm.IWAppFormRender(Sender: TObject);
  TIWContextHelper.AddCSSFile(Self.PageContext, 'qTip.css');
  TIWContextHelper.AddJavaScriptFile(Self.PageContext, 'qTip.js', True);
1) IWContextHelper expect CSS files to be in /files/css/ subfolder, and JavaScript files to be in /files/js/ sub-folder. This can be changed in IWContextHelper.
2) If the default sub-folders are kept, then you have to set AllowSubFolder of your ServerController instance to TRUE.
3) The JavaScript file must be added to the BODY part of the HTML document. That's why the third parameter of TIWContextHelper.AddJavaScriptFile is TRUE.

Deploy your application

You will have to deploy qTip.js and qTip.css with your application, inside the sub-folders used in step 1. And you are done!


This is a test using the default "Features" Intraweb demo. I've set the hints to a multi-line string, for each cell in the grid:

Download simple demo

You can download a simple demo with all required files here.

Further notes

1) The project was created using Delphi 2006 + IW 10.0.23 but should run in newer versions without problems.
2) qTip, as included in the zip file, will "qTip-ize" a, label, input and td HTML tags. If you want to add other tags, modify the qTipTag var, inside qTip.js file.
3) Hint windows style can be customized editing qTip.css file.


Tuesday, March 6, 2012

Intraweb TextToHTMLStringLiteral in a multi-threaded test

In a recent blog post (Speeding Intraweb up again) I wrote that I suspected that in a multi-threaded test, running in a multi-core computer, the modified version of TextToHTMLStringLiteral would perform even better. So I decided to test it in this context. Now I can tell you that I was right ;-)

The test

Today I created a new multi-threaded test. Two threads running in parallel doing the same thing: Taking a string and using TextToHTMLStringLiteral to convert it multiple times, inside a tight loop. During the first test, both threads used the original TextToHTMLStringLiteral implementation. In the second test, I used my modified version. Both threads execute the following code:
procedure TMyThread.Execute; 
  FCount := 0;
    FStr := 'ABCD XPTO %^&* ABCD XPTO %^&* ABCD XPTO %^&*';
    FStr := TextToHTMLStringLiteral(FStr);
  until FCount = MaxCount;

CPU Utilization

The fist thread CPU utilization chart can be seen below.

Note that there are two threads but CPU utilization is only slightly above 50%.

Now the second CPU utilization chart:

Note that now CPU utilization is almost 100% (and always above 90%). Of course there are a few LOCKed instructions here and there, but it is clearly more multi-core friendly.

The results

Original TextToHTMLStringLiteral time: 24.4 seconds (using ~ 55% CPU x 2)

Modified TextToHTMLStringLiteral time: 3.6 seconds (using ~ 98% CPU x 2)
The time measures showed that the modified code performs almost 7 times better than the original, using 2 threads (It was 6 times better in a single thread app). We can expect that the more cores you have, the bigger will be time difference.


We can see clearly that Delphi code written with special care, specially code dealing with string writing/concatenation, can greatly improve performance and make the compiled code much more multi-core friendly.

Monday, March 5, 2012

TCodeRedirect redux

In my latests posts I was dealing a lot with Delphi classes runtime patching. Thanks to Andreas Hausladen, Chau Chee Yang (among others) most of the hard work is already done, but...
I've created a new and different version of Yang's TCodeRedirect that I consider easier to work with, because it works as a singleton. This way, you won't need to create a new TCodeRedirect instance for each patch you apply during runtime.
I'm releasing the source code of this new TCodeRedirect class because many Delphi developers may use this technique in their own applications and frameworks, so here is it!
Chau Chee Yang covered most of TCodeRedirect utilization cases in his original blog post. What I've changed is: Using Yang's code you would call TCodeRedirect this way:
TCodeRedirect.Create(@MyOldProc, @MyNewProc);
With my new TCodeRedirect, you may use it with another syntax, like this:
TCodeRedirect.GetInstance.AddPatch(@MyOldProc, @MyNewProc);
This code was tested with Delphi 6, 7, BDS 2006 and XE2 but should work with other versions as well. Enjoy!

Modifying Intraweb Patches

Today I received a message from a blog reader, pointing that my patches were not compiling under more recent versions of Delphi, specially Delphi 2010. Really, I haven't checked D2010 compatibility before releasing my code. The problem is not in IntrawebPatch.pas but inside my modified RtlVclOptimize.pas (from Andreas Hausladen). Trying to compile it under Delphi XE2, for instance, showed me that it has many incompatibilities...
So, I decided to create a new patching mechanism. It is not original, but my own version of CodeRedirect, based on Chau Chee Yang TCodeRedirect (his work is also based on Andreas Hausladen, I guess).
This code was tested with BDS 2006 and XE2, and IW 10 and XI but should work with other versions as well.

The downloads

You can download a zip file containing IntrawebPatch.pas, CodeRedirect.pas and other required files here.


Friday, March 2, 2012

Speeding Intraweb up again

A few weeks ago I wrote about how to hack Intraweb classes to speed it up a little. I've found another method that could be faster, using a similar approach: This method is TextToHTMLStringLiteral, from TIWBaseForm class, declared in IWBaseForm.pas. This method is used inside the TIWForm.GenerateForm method, once for each hidden field:
class function TIWBaseForm.TextToHTMLStringLiteral(aText: string): string;
  f : integer;
  Result := '';
  for f := 1 to Length(aText) do begin
    if (AnsiChar(aText[f]) in ['A'..'Z', 'a'..'z', '0'..'9', ',', '.', '-', '_']) then  begin
      Result := Result + aText[f];
    end else begin
      Result := Format('%s&#%d;', [Result, Ord(aText[f])]);
Here we can see the same pattern we found in the previous modified methods. We will use the same kind of construction - and the same runtime patching technique - to replace this method with a faster one, during execution. The new TextToHTMLStringLiteral is shown below:
class function TIWBaseForm.TextToHTMLStringLiteral(aText: string): string;
  NoConversion = ['A'..'Z', 'a'..'z', '0'..'9', ',', '.', '-', '_'];
  Sp, Rp: PChar;
  SetLength(Result, Length(AText) * 8);
  Sp := PAnsiChar(AText);
  Rp := PAnsiChar(Result);
  while Sp^ <> #0 do
    if (Sp^ in NoConversion) then
      Rp^ := Sp^;
    end else
      Inc(Rp, FormatBuf(Rp^, 7, '&#%.4d;', 7, [Ord(Sp^)]) - 1);
  SetLength(Result, Rp - PAnsiChar(Result));
I will not enter in much detail about the implementation, but I think it is quite simple, and it uses the same approach used in the previous post.

The results

Again, I did a simple benchmark application so I could see if there is some gain using this new method.

Original code: 5.10 seconds

Modified code: 0.82 seconds

for 1,000,000 function calls. Quite impressive, isn't it? Of course, this benchmark used a specific string, and a different string will give different results.

This time, the new method is more than 6 times faster than the original method, in a single thread. I suspect, based on my previous post about String concatenation in Delphi, that the new code can be even faster - and more multi-core friendly - in a multi-threaded application (like IW applications). I will create this benchmark application and write a blog post about the results soon.
Checking one of my IW applications, I've found forms with more than 100 hidden fields in it. The time gain will be insignificant (less than 1 millisecond), but this is not the point. The main point when I try to make my applications faster, specially Intraweb applications, is: make it run as fast as you can AND make it more multi-core friendly.

The downloads

You can download an updated IntrawebPatch.pas unit containing this new patch (and the old ones) here.