Tips for new TMS Web Core developers and in particular for seasoned Delphi developers

I can't remember the exact scenario, but I think it's because of the Optimization option that some of the code doesn't get included in the final build of the project. I noticed this when sometimes the code doesn't work as expected, and when I put a ShowMessage along with the value that I'm having an issue with, it works fine. So what I did was adding something like this inside the code:

if False then
begin
  aa;
  bb;
  cc;
  ...
end;

Doing this forces the compiler to include these blocks and as such the project works as expected again.

1 Like

Yes! I've found this as well, particularly when Delphi procedures or functions are only called by Javascript code, the compiler thinks it isn't used, so it excludes them. Also, when you use variables in a procedure but only in the Javascript code, like in your example about local variables, the compiler issues a Hint about that which is kind of annoying because it is otherwise perfectly valid. I included my workarounds for both of these in a writeup here. I hadn't thought of just wrapping the code in an if-then block, good idea!

Get accustomed to using Timers. After decades of Delphi VCL work, there were maybe a handful of situations where I had any need for Timers (I didn't do much work with multithreading). In TMS Web Core, everything can (or should) be asynchronous when it comes to updating the UI. For example, there's (deliberately) no sleep function but you can use a Timer to work in a similar way, checking for something to change before initiating some other UI update.

FontAweseome is... awesome. Maybe poorly named though, as its purpose is less about fonts and more about icons. Gone are the days spent fiddling with glyphs or trying to cleanup an icon so that it looks good at 16x16 and 256x256.

NOTE: You do have to explicitly add FontAwesome to your project. Lots of options here, but one is to add something like this to your Project.html file:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/css/all.min.css">

Then, wherever you need an icon for something, just add in the FontAwesome tag, typically in the Caption property of a control, and you've got an icon:

LoginButton.caption := '<i class="far fa-sign-in-alt"></i> Login';

There are thousands of icons to choose from, and different styles, and even more with the paid plans. Plenty to work with even with the free option. I particularly like the duo-tone icons included in the paid plan.

But wait, there's more! There are a bunch of things you can add to the classes to change the appearance of the icons. Like modifiers to make the icons larger or smaller. Or offset or rotate them in some direction. And they work with Bootstrap modifiers as well.

You can even make a trivial kind of busy indicator by adding the 'fa-spin' class which causes the icon to spin. Works best with icons that were designed for that purpose:

WaitButton.caption := '<i class="far fa-circle-notch fa-spin"></i> Please Wait';

And you can add multiple icons at one time, overlay them, do all kinds of things. Pretty awesome.

2 Likes

I use a TWebTimer and a TWebWaitMessage when closing PopUps (because the close too quickly :-).

So when Save is clicked

SaveTheWork;
TWebWaitMessage.Show;
TWebTimer.Enabled;

Then in TWebTimer.OnTimer

TWebWaitMessage.Hide;
ModalResult := mrOK;
1 Like

Browser debugging tip.

Scenario: You have a dynamically displayed control on the page that only appears after some kind of user action. Like a popup menu or a combobox dropdown or something like that. And you want to use the browser DevTools to look at or test the CSS properties of the control in its activated state. But when you click on the element in the DevTools node tree, the state of the control reverts back and you can't see the properties you're after.

One option is to use the DevTools console to trigger a debug event after a short delay. Just paste this into the console and hit enter. You'll have a three second delay before the debugger pauses, giving you enough time to bring up the menu or click on the combobox to show the dropdown. You can then look at that element's properties, adjust settings, and so on, without it reverting back to its normal state.

setTimeout(function() { debugger; }, 3000);

Of course you can also adjust that 3000 to something larger if you need more time to select the appropriate control.

2 Likes

Right Aligning Text in TWebDBTableControl with Bootstrap

The table control ignores the alignment settings on the dataset fields. So if you want to align right for numbers, then set the TWebTableControlColumn.ElementClassName := 'text-end'

1 Like

Perhaps another tip is just that new TMS Web Core projects in general benefit greatly from the use of Bootstrap to begin with. It is helpful that TMS Web Core recently started including a new project template specifically for Bootstrap. I frequently use this Bootstrap Cheatsheet as my go-to resource for looking up bootstrap classes. Are there any other good recommendations other than of course the Bootstrap site?

There are also other examples here in the Support Center such as here and here that relate to where Bootsrap can come in handy in TMS Web Core, in addition to the above post of course.

1 Like

Naming Element Id

If you are using popups, make sure that the element ids are not just unique within the popup but within the form that popup is displayed on.

2 Likes

TWebWaitMessage

If you use the TWebWaitMessage.URL property remember to clear the Picture property otherwise the picture will be created in the project output.

1 Like

Upgrading TMS Web Core to the latest version

When installing a new version of TMS Web Core, it is best to first completely remove the old version using the usual Windows Add/Remove Programs process, naturally while Delphi is not running of course. Then install the new version of TMS Web Core as basically a fresh install. There can be problems if this isn't done, as can be sen in a number of posts here in the Support Center over the past while.

It is also a good idea to pay attention to the release notes so that you can check your app for any areas that may be impacted. While my personal experience with updates has been overwhelmingly positive, meaning nothing has broken as a result of an update, there have been instances where some small touches were needed to adjust alignments or that kind of thing. Also good to do a quick review of your app after an update just to make sure there are no surprises down the road.

2 Likes

Use Third-Party Javascript Libraries

One of the most powerful aspects of TMS Web Core is its ability to easily support popular Javascript libraries with very little effort, greatly expanding the capabilities of your app in any number of ways. TMS includes direct support for a small handful of libraries through the Project Manager's "Manage Javascript Libraries" function. Popular libraries like Bootstrap, Ace Editor, and now even Font Awesome can be added to your project with just a couple of mouse clicks. Some of these even have wrappers that make it easy to add functionality to your app using familiar IDE components.

For the many thousands of other JS libraries that are not directly supported or do not have a ready-made wrapper, they can still be used without much effort at all simply by referencing them in your project's HTML file and adding the JS code to your project as you would any other file. This is in part what the "Manage Javascript Libraries" is doing behind the scenes.

Many JS libraries are available through "Content Delivery Networks" or CDNs. These can be used to add libraries to your project without having to include the files in your own distribution - they are loaded by the client at runtime, reducing the size of your app that has to be served up by your own hosting service and potentially increasing performance by having the libraries loaded from a server that is closer to where the client app is running. For example, an earlier posting in this thread describes how to add the Font Awesome library to your project using the JSDelivr CDN.

Just keep in mind, when using a CDN, that the library is managed by someone else which introduces some trade-offs. If you link to the "latest" version of the library, it may be updated with breaking changes that may adversely impact your app. If you instead link to a static version, you might not get the benefit of security updates that may also adversely impact your app, just as if you hosted a copy of the library yourself.

How you use the actual JS library is of course dependent on what the JS library does. Some libraries are discussed in detail elsewhere in this Support Center forum including examples and tips on getting the most of the library within the TMS Web Core environment.

Are you using a library that isn't already included in TMS Web Core? Or do you have questions about how to integrate a library you're interested in? Would be interesting to see what libaries are used most by TMS Web Core users. At the moment, I've got more than a dozen in my project. The usual suspects are there like Font Awesome, Bootstrap and JQuery. But I've also been using popular libraries like Tabulator, CodeMirror, FlatPickr, SunEditor, Toastr, File-Save, PrintJS and others.

4 Likes

ElementID - Tread carefully!

Most TMS Web Core components have an ElementID property availablie. This maps directly to the HTML element's ID property when the page is generated. It is important to first note that an HTML ID should be unique in a page (specifically the DOM) and that it is case-sensitive. If this property is left blank, TMS Web Core will generate a unquie ElementID when needed, but it isn't a fixed value. It may very well change in a subsequent build. So if you need to use it, be sure to set it yourself.

One use-case for this property is to connect a TMS Web Core component to an element within an HTML template. The docs explain this pretty well, and the IDE has tools for managing these linkages.

Another use-case is to use the values in your custom CSS. So if you have a component that you want to apply custom CSS styling to that you can't otherwise do directly in the IDE, you can assign something like myElementID to the ElementID property of the component and then in your CSS you can use the

#myElementID { }

selector to add whatever custom CSS you might need. Naturally this should be used somewhat sparingly as you wouldn't want to have to use that specific of a CSS selector often as it kind of defeats the purpose of CSS. But it can certainly come in handy to fix up little tweaks here and there.

Yet another use-case is when using third-party JS components. These often need to be linked to an HTML ID. So if that happens to be one of your components, this is a way to make that happen. Sometimes, like with Tabulator, not filling in this ElementID can introduce unexpected behaviour, so a good thing to keep an eye on if you're having troubles.

There can be problems though, particularly if you use the same ElementID on multiple components and they somehow make it onto the same page at the same time. So for example, let's say you use the same ElementID naming convention across different forms - maybe just the name of the component. If two forms get loaded up at the same time with components that have the same ElementID, conflicts will arise that may not be that easy to track down. In Delphi, if you have two controls on different forms with the same name, it isn't really a problem because they have the form name to use as a way to descriminate between them, but that's not the case here.

So if you're loading up multiple forms into the same page at the same time, this is a good thing to keep an eye out for. And if you happen to be loading the same form multiple times, consider either not assigning the ElementID or assigning it to something unique when the form is created. Here's an example of the kinds of problems that can crop up unexpectedly when these ElementID naming collisions occur. Usually there's a console log message about it, but sometimes it isn't so obvious.

And finally, if you happen to use FNC components, the ElementID property (and others) are conspicuously absent. To work around that, you can try something like adding a TWebHTMLDiv component to your form and then add the FNC component to that and be on your merry way.

3 Likes

Excellent advice. Be particularly careful when calling popups - they make look like their own form, but they are part of the webpage being displayed when they are called. Learnt the hard way.

1 Like

If you use javascript (asm) in a function remember that result isn't defined, you have to use Result as JS is case sensitive. So

This works

class function THTMLHelper.elementHeight(const aElementId: string): integer;
begin
  asm
    Result = parseInt($("#" + aElementId).height());
  end;
end;

and this doesn't

class function THTMLHelper.elementHeight(const aElementId: string): integer;
begin
  asm
    result = parseInt($("#" + aElementId).height());
  end;
end;
5 Likes

In addition to that, in order to keep the Delphi IDE happy and not break the LSP, I always wrap my asm end blocks with {$IFNDEF WIN32} {$ENDIF}, so it looks like this

  {$IFNDEF WIN32} 
     ASM 
     ...
     END; 
  {$ENDIF}

Also, I think it is good practice to first convert all function parameters to local variable before accessing them in JavaScipt, like so

Function Foo(Bar1 : String; Bar2 : Integer) : String;
Var
 LBar1,LResult ; String;
 LBar2 : Integer;
Begin
 {$IFNDEF WIN32} 
   ASM 
    LResult = yourjsfunction(LBar1,LBar2);
   END; 
 {$ENDIF}
 Result := LResult
End;

Still, the compiler is unhappy and gives you unused variables warnings. To also get rid of those, use:

If LBar1='' then;
If LBar2=0 then;

So in total, you end up with:

Function Foo(Bar1 : String; Bar2 : Integer) : String;
Var
 LBar1,LResult ; String;
 LBar2 : Integer;
Begin
 If LBar1='' then;
 If LBar2=0 then;
 {$IFNDEF WIN32} 
   ASM 
    LResult = yourjsfunction(LBar1,LBar2);
   END; 
 {$ENDIF}
 Result := LResult
End;
2 Likes

Really catching ALL exceptions

In regular Delphi, catching a generic exception is done like this

Try
 ...
Except on E:Exception do
 Begin
 End
End

Well, in the JS environment, this does in fact not catch ALL errors. Errors created by the JS environment are NOT derived from class Exception and therefore On E:Exception does not catch these errors. As the Delphi ExceptObj is also not available, some JS helps again, like so:

Function GetMessageFromException(E : Exception) : String;
Var RMsg : String;
Begin
 RMsg := '';
 {$IFNDEF WIN32}
 ASM
  {if (!pas.SysUtils.Exception.isPrototypeOf(E)) {
   RMsg = E.message;
  } else {
   RMsg = E.fMessage;
  }}
 END;
 {$ENDIF}
 Result := RMsg;
End;

{---------------------------------------}

Procedure Foo;
Var e : Exception;
Begin
  Try
    ...
  Except
   {$IFNDEF WIN32} ASM e = $e; END; {$ENDIF}
   Console.error(GetMessageFromException(e));
  End
End;
6 Likes

Using [async] and await()

One key difference in developing web apps as compared to VCL apps is the asynchronous nature of web browsers. Maintaining a good user experience is paramount, so every effort is made to minimize any kind of blocking action. As a result, many function calls often return immediately with a JSPromise result, long before the actual work of the function is completed. This can be confusing or problematic as the flow of your code doesn't necessarily follow the order of the statements being executed.

These kinds of functions typically have callback functions that get called when the work of the funciton is actually completed. This is needed to make apps work in a predictable way but really makes a bit of a mess of things when it comes to trying to understand program flow, particularly when there are callback functions everywhere.

Wrapping these kinds of function calls with await() makes it possible for the program to simply wait at that point in the code for the work of the function to complete before continuing on to the next statement. It does it in a non-blocking way so that the user experience isn't negatively impacted. This often removes the need for callback functions and code readability goes up dramatically.

If you have a function or a procedure that includes an await() call in it, it must be declared initially with an [asyc] attribute. Failing to do so will generate an error at compile time, so not at all hard to fix if you forget.

[async] procedure WebButton1Click(Sender: TObject);

Also best to not try anything fancy here. Keep [async] on the same line as the declaration :slight_smile:

3 Likes

Bootstrap Tooltips

While TMS Web Core supports hints as they work in the VCL, sometimes a little more control over the look of them is desirable. If you're using Bootstrap in your project, you can use their tooltips as well. As this would likely happen often, a procedure to do this might look like the following.

procedure SetBootstrapTooltip(btn: TWebButton);
var
  ElemID: String;
  ToolText: String;
begin
  ElemID := btn.ElementID;
  ToolText := btn.Hint;
  asm
    var btn = document.getElementById(ElemID)
    var tooltip = new bootstrap.Tooltip(btn, {
      title: ToolText,
      placement: 'right',
      customClass: 'MyCustomToolTipClass',
      delay: { "show": 1000, "hide": 250 }
    });
  end;
end;

Setting the (optional) custom class allows you to add your own CSS to the tooltip div that is generated. And you can change the placement to 'right', 'left', 'top', 'bottom' or add further tweaks using the CSS margins. Many other options can be found in the Bootstrap 5 Tooltip documentation.

Oh, and if you want to fiddle with the styling while using the browser debugging tool, see the Browser Debugging Tip near the beginning of this thread to see how to have the tooltip stay visible after you've moved the cursor away from it.

1 Like