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

TMS Web Core is an increditble tool for developing web apps using much of the same tooling and knowledge that has become second nature for Delphi developers. But developing a form for a web page is a very different thing than developing a form in a VCL app or even a FireMonkey app. Different doesn't necessarily mean worse, just different. There are quite a few things that are likely to trip up even the most accomplished Delphi developer along the way.

This post is meant as a repository for this kind of thing, so I'd like to invite everyone to add something here that they've run into that might help others along the way. Or maybe a link to another post either here in the TMS Support Center or elsewhere that was helpful in tackling a particular problem. Mostly thinking of simpler just-getting-started kinds of problems here, not esoteric only-ever-applies-to-my-project-on-the-second-full-moon-of-an-odd-numbered-month kinds of problems :slight_smile:

4 Likes

I'll offer up one to start.

Tab order in web pages is a different thing than in VCL apps. What I've found, when I have a bunch of edit controls or something, is to just give them an arbitrary set of TabOrder values. So 100-110 or something. So at least when one of them is active, the previous/next tab will go to the previous/next control in that particular set. This works particularly well if you've got controls added to panels or other DIVs where the usual ordering mechanisms don't work partiucarly well at all.

2 Likes

You can't just use a global variable inside an asm block. First, you need to declare a local variable, then you have to pass on the global variable value to the local variable, and then you can use that local variable in the asm block.

2 Likes

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