Enable HTTPS in your REST server with Free SSL/TLS Certificate from Let's Encrypt


Thursday, April 19, 2018

One of the most important aspects of a REST API server is security. It should be mandatory that your data is encrypted while being transferred between client and server. And the “standard” way to do such encryption is using HTTP Secure (HTTPS).

To support HTTPS in your server, you need an SSL/TLS certificate. Not so long ago, this was a task that was relatively annoying/hard to do, and it would also require you to pay yearly fees for that. That’s not true anymore, since the release of Let’s Encrypt. From their website: “Let’s Encrypt is a free, automated and open Certificate Authority”. What does that means, basically? You can now have SSL/TLS certificates for free. And automatically!

The purpose of this article is to give you direct step-by-step instructions for you to install your SSL/TLS certificate for any TMS Sparkle-based server (like TMS XData, for example). Thus, if you want more info about Let’s Encrypt and how its system works, please visit their web site to learn more about how it works.

To install a Let’s Encrypt certificate in your TMS Sparkle server, we are going to use a tool named win-acme (Simple ACME Client for Windows, WACS - Windos ACME Simple). It was formerly called letsencrypt-win-simple (LEWS). The video presented at the end of this article was made when the tool still had its old name, so also some screenshots of this article will reflect that as well. But the process remains mostly the same with very small differences mentioned in this article.

In this example we are going to secure a server that responds to address http://app.devgems.com. To start with, you must have a domain name and your server must be working and responding HTTP requests to that domain name. Here is for example a screenshot of a browser request to that server. Note that it’s not secure yet

To download win-acme, go to the Releases link of its GitHub page https://github.com/PKISharp/win-acme/releases and download the latest version available. By the time this blog post was written the file name should be win-acme.v1.9.10.1.zip, but you might find a similar name with a most recent version. In case you want to use the exact version used for this server (to folllow exactly what is being done in the video and in the screenshots) you can download it from this link: https://github.com/PKISharp/win-acme/releases/download/v1.9.7.0-beta10/letsencrypt-win-simple.v1.9.7.0-beta2.zip.

Upload the downloaded file to the Windows server running your TMS Sparkle server, and extract it to any directory, for example C:\letsencrypt. You should have a folder with the files like the following:

Notice the selected file above, named letsencrypt.exe.config. It’s the config file for the tool. In most recent versions, that file would be named settings_default.config. Whatever is the name you have there depending on your version, open that file in a text editor (Notepad, for example) and search for a setting named CertificateStore. Change the value of that setting to “My”. The section of the file should look like this:

<setting name=“CertificateStore” serializeAs=“String”>

In the same folder, create a file named bindcertificate.bat with the following content:

netsh http delete sslcert ipport=
netsh http add sslcert ipport= certhash=%1 certstorename=%2 appid={00112233-4455-6677-8899-AABBCCDDEEFF}

The GUID in appid above could be anything. You can change to a GUID value you want, or just use what's there. You should then have your new file in the folder:

Now open command-line with administrator rights, go to C:\letsencrypt folder if you are not already there, and run the following command. Please don't forget to replace <yourdomain> by your real domain. In the case of our example here, it would be app.devgems.com

letsencrypt --plugin manual --manualhost <yourdomain> --validation selfhosting --script "c:\letsencrypt\bindcertificate.bat" --scriptparameters "{5} {3}" --usedefaulttaskuser

Depending on the version of the tool you are using, you might be asked to enter your e-mail address and agree to the terms of condition. The e-mail address will only be used by Let's Encrypt to notify you about certificate expirations. You will only have to do that for the first time.

If everything went ok, you should have an output like this indicating the certificate was generated and installed in your computer:

You can open the MMC Snap-in to check that your certificate is installed properly:

And that's it! Not only the certificate is now installed in your server computer, but it is also configured to your TMS Sparkle server and will be renewed automatically. Your server now supports HTTPS forever! If we browse to our server address now using HTTPS, we can confirm it's using the new certificate:

The following video shows in much more details the operation described above, including using the "test" flag to test the whole process in a test environment before using the production one.

Wagner R. Landgraf


This blog post has received 6 comments. Add a comment.

TMS announces a new training day: TMS RADical Web Day in Germany


Tuesday, April 17, 2018

We're excited to inform we are organizing a new training day: TMS RADical Web Day in Germany.

Learn everything about fascinating new ways to create modern new web development with Delphi!

When you're a passionate Delphi developer and interested in applying your skills for creating web applications, you cannot miss the TMS RADical Web Day.

On the TMS RADical Web day, you can:

  • Discover how to apply your Delphi knowledge to create high performance web applications.
  • Learn how RAD techniques can be applied to go web with components.
  • Learn how you can integrate existing web frameworks like jQuery.
  • Experience how to use REST services from your web applications.
  • Learn how TMS XData or Embarcadero RAD server data can be used from web apps.
  • Learn how you can now use FNC UI controls not only for VCL, FMX, LCL apps but also for the web.
  • Mingle with fellow Delphi developers.
  • Get a glimpse of the roadmap of future developments under the TMS RADical Web umbrella.
  • Talk in person to the experts from TMS software.
  • Receive a gift with the fully functional trial version of TMS WEB Core, samples and documentation.
  • Get 20% discount for one product of your choice, for your new licenses!


Sessions are presented by:

  • Bruno Fierens, CTO of tmssoftware.com + Embarcadero MVP
  • Roman Kassebaum, architect tmssoftware.com + Embarcadero MVP
  • Wagner Landgraf, architect tmssoftware.com + product manager TMS Business Subscription
  • Adrian Gallero, architect tmssoftware.com + product manager TMS Flexcel product line
  • Leon Kassebaum, Delphi & TMS WEB Core enthusiast


This extraordinary event will take place at Hotel-Residence Klosterpforte, Klosterhof 2-3, 33428 Harsewinkel-Marienfeld, GERMANY. The location is easily accessible via the A2 motorway.

TMS software has chosen for Hotel-Residence Klosterpforte as it offers an inspiring and peaceful place in the midst of the nature and green to maximize your concentration & creativity. Facilities for hotel rooms at the event are optionally available for attendees.


Early bird: 195 EUR (before May 1, 2018)
Regular price: 245 EUR

The registration fee includes a full day access to the sessions and coffee/drinks during this day. A 3 course hot meal is served for lunch.

Seats are limited. Don't delay your registration and reserve your seat!

Get more information or make your reservation here!

We look forward to see you!

Bruno Fierens


This blog post has received 4 comments. Add a comment.

Object Pascal: Immutable State


Photo by Morgan Harper Nichols on Unsplash

In object-orientation, an object can be considerable immutable even if it... changes.


Learning a complex concept tends to be better absorbed if we divide the concept into small parts.

For example, when we begin to learn English, we are taught that to refer to something that happened in the past, we must use past verbs to make affirmations or use the modal verb did or did not to ask questions or denials. This is the verbal tense Past Simple.

Example: "I saw this movie".

Then, as you begin to study the language more, you discover that there are many other ways of expressing something that has occurred in the past. For example, the use of verb tense Present Perfect.

Example: "I have seen this movie".

Here we see the use of the modal verb to have and the use of Past Participle of the verb to see.

Both sentences say the same when we translate to Portuguese, for example, but the second sentence can be considered more correct than the first one — you only discover this after you learn the Present Perfect. But this is not an English article, so let's go back to object-orientation.

This introduction is to show you that we must first learn a concept in its simplest form; then we improved.

Concepts of Immutability

When you've learned about immutability you may have thought that an object is immutable if, after it is created, nothing is changed inside it or that the object always returns the same information when a method is called.

Well, that is not so simple.

Unlike functional languages, where everything is immutable by default, in object-orientation this concept may be broader.

Let's take a look in some concepts.

External content

A class that represents a file could be immutable in two ways:

  TFile = class
    FFilePath: string;
    FStream: IDataStream;
    constructor Create(const FilePath: string);
    function Stream: IDataStream;
  constructor TFile.Create(const FilePath: string);
    FFilePath := FilePath;
  function TFile.Stream: IDataStream;
    if not Assigned(FStream) then
      FStream := TDataStream.New(FFileName);
    Result := FStream;

In the code above, the TFile.Stream: IDataStream method will always return the same value read on the first run.

The object is immutable and constant.

But, what if we change the method like below, the class will continue been immutable?

function TFile.Stream: IDataStream;
  Result := TDataStream.New(FFileName);

Yes, for sure.

Although the result of the method may be different for each call — the contents of the file can be changed by another process — the object would remain unchanged because its state (FFilePath attribute) has not been changed.

It is immutable, but not constant.

The same concept applies to content coming from a Database, Website, etc.

Content in Memory

Can a list of objects be considered immutable if we add items after it is created?

Let's take a look in this code:

TDataParams = class(TInterfacedObject, IDataParams)
    FList: TInterfaceList;
    constructor Create;
    class function New: IDataParams; overload;
    destructor Destroy; override;
    function Add(Param: IDataParam): IDataParams; overload;
    // ...

{ TDataParams }

constructor TDataParams.Create;
  inherited Create;
  FList := TInterfaceList.Create;

class function TDataParams.New: IDataParams;
  Result := Create;

destructor TDataParams.Destroy;

function TDataParams.Add(Param: IDataParam): IDataParams;
  Result := Self;

The TDataParams class encapsulates a list of TInterfaceList.

At each inclusion of a new item, the in-memory persistence of the objects is delegated to this internal list.

Do you think we're changing the object state (FList)?

We are not.

The reason is that we are not redefining FList. We are not creating a new list.

A list of items is being created in memory — memory blocks — but the initial address of FList remains intact.

Also, FList is a secondary attribute, so we could even redefine this attribute without being in disagreement with the principle. But, if the list instance were passed in the constructor, then we could not redefine the object because it would be considered as a primary attribute.

Immutability of the State

If even after some internal or external changes to the object, it is still considered immutable, how do we know if we are not violating the principle of immutability?

Simple: If the state of the object, ie., its primary attributes, is not changed/redefined, then it is immutable.

Once an attribute is instantiated, it can not have its memory address changed. These attributes will be initialized in the constructor of the class and can never be reinitialized.

Your object must be faithful to arguments passed in the class constructor, however it is free to work and respond to whatever you want in your methods.


Immutability, in object-orientation, is not about internal or external changes to the object, but about not changing the primary state of it.

Do not confuse data with object state.

So, we need to work with these concepts in mind.

See you.

Marcos Douglas B. Santos


This blog post has received 4 comments. Add a comment.

Object Pascal: Redefining Classes


Tuesday, April 03, 2018

Photo by Mpumelelo Macu on Unsplash

The redefinition of classes is a practical method to minimize the collision of names between classes, even using short identifiers.


The Object Pascal language has a very useful feature which, I believe, is not widely used by developers.

I do not know if there is a specific name for it, but I named it "redefining classes".

In fact this feature can be used to redeclarate classes, constants, and even functions.

However, in object-orientation, we basically only use classes. So, forget the rest and let's focus on them.

Let's take a look at some techniques that can be applied using such a concept.


Imagine that you want to use a class of some Lib but, this class has the same name — example TSmartMemory — of one of your classes, which you already use throughout your code. What to do?

The first option is to never use both classes on the same unit. But maybe you're not that lucky.

The second option is to prefix one of the Classes with the unit name — very common to see this in Java projects — for example:


      M := TSmartMemory.New;  // your class
      L := LibMemory.TSmartMemory.Create;

The third option, which I use most of the time, is "rename" the class of Lib to a nomenclature that does not collide with the nomenclature already used in my project. Let's take a look at an example:

    unit MyMemory;


      // TSmartMemory from LibMemory
      TLibStartMemory = TSmartMemory;

      // my new class
      TSmartMemory = class
        // ...

In the example above, both classes are declared in the same unit in the project — the MyMemory — and the project can use both classes without nomenclature collision.

      M := TSmartMemory.New;
      L := TLibSmartMemory.Create;

Using this technique we avoid name conflict, which is very useful.

Instead of using the real class name, we can give it an alias. The code stay cleaner, simple and using short identifiers, because we don't need to use the unit name as a prefix.

The C# language has something very similar which makes me wonder where the main architect of the C# language took out this idea.


Many times we need to use a composition of different classes to solve a problem.

But, if each one of these classes has been declared into a different unit, we will need to declare all those units to get access for each one of those classes.

For example. We need of TClass1, TClass2, and TClass3. Each one of them into different units, Unit1 , Unit2 e Unit3, respectively.

    unit MyUnit;

      Unit1, Unit2, Unit3;  

If we need to use that composition in many places of the code, we will always have to remember which units these classes are in.

Another option is to make classes visible, redeclaring all of them in a single unit, for example Unit123, so that we can use them in a simpler way but still maintaining each implementation in different units.

    unit Unit123;

      Unit1, Unit2, Unit3;

      TClass1 = Unit1.TClass1;
      TClass2 = Unit2.TClass2;
      TClass3 = Unit3.TClass3;

Now, just use Unit123 in the code to have access to all 3 classes that previously could only be accessed in different units.

    unit UnitTest;


This technique is very useful for simplifying an API, giving developers only a few classes for use in very specific contexts.


There are times when we want to use class inheritance — even though Object Composition is the best choice — however, we would like to use the same name of the ancestral class.

Let's imagine that we are developing a software and, into one of its units, has a class that represents a PDF file. We name this class as TPDFFile.

However, we know there may be tens or hundreds of Libs already working with PDF. So, let's named one of them of PDFLib, for example.

In PDFLib we have a class called TPDFFile which is exactly the same name that we have already decided that will be used in our software, but the project architect says that our class should inherit from PDFLib.TPDFFile.

I guess you already know the answer:

    unit MyPDFUnit;


      TPDFFile = class(PDFLib.TPDFFile)
        // more methods

By prefixing the class of Lib, we can identify it differently from our own class declared in the same unit.

The TPDFFile class is now an extension of PDFLib.TPDFFile which belongs to an external Lib. But, for all the rest of the code in the project, there will be only the MyPDFUnit.TPDFFile class to represent a PDF.


The technique that I will show you now was already used even before to have a syntax for class helpers.

For example, imagine that you want to include new properties or methods in TEdit class. The first thought is to use inheritance to create a new component. However, you do not want to replace each TEdit in all TForm that already exist in the project. So, what to do?

The answer is still to use inheritance. But there is a trick or hack that I will show in the technique below. But I would like to remind you that this should be used very sparingly. I only used this technique very seldom and only for classes that represent widgets, that is, classes of components that are used in Forms.

To extend a TEdit without creating a new class and without having to change the Forms, just use the same technique above:

    unit MyStdCtrls;

      TEdit = class(StdCtrls.TEdit) // or Vcl.StdCtrls.TEdit
        // more methods and properties

You do not need to change widgets in Forms. The design rendering of the Form will work using the resources (* .lfm * .dfm) correctly.

There is one trick you can not forget: In each of these Forms you will need to declare your unit after of the actual StdCtrls unit.

    unit MyForm1;

      StdCtrls, MyStdCtrls;

This hack is required for the compiler to find the declaration of your unit before it is found in the default StdCtrls.

That is why it is so important to have an order in the declaration of the units.

Optional Nomenclature

This technique can be considered the opposite of the first one shown in this article, ie., instead of you "renaming" a class belonging to another Lib, you will give to the developer some naming options for the use of their classes.

Imagine that you've coded a package with some classes. In this package you like to use simple names such as TStream, TMemoryStream, TString, TInteger, etc.

See that we already have some possible "problems" here. Let's see:

1. The TStream and TMemoryStream classes already exist in FPC or Delphi and, because that, there may be a collision of names if developers use your package, as it is very likely that they are already using such default classes names in their projects;

2. The TString and TInteger classes are classes with very simple or generic names and, again, it is likely either some other Lib or even the projects of these developers already use such names.

The problem here is the name clean and short. While these names are perfect, they have the great potential of generating some name collision problems.

But when you are creating your package, you have to abstract the external world. The context of the package that should influence the nomenclature of classes and not the external world that might use it!

I "fought" against this problem for many years. Before I "discovered" this technique, I prefixed my Classes with 1, 2 or 3 letters — this seems to be the standard used by all component developers in the market — however, you can find out over time that your "prefix" chosen for your classes have already been used in another Lib from third parties.

Imagine you have to use verbose names like TXyzMemoryStream throughout your code to then find out that Xyz is already a prefix used by a large "component maker" on the market.

Then, I discovered that the Object Pascal language already had an answer and I could have the best of both worlds. I could use simple, compact and clean names inside my context (package, project, lib) but giving to potential developers a more verbose, but with less possibility of collision of names, if so desired.

Let's look at an example: Imagine that you have a package with a class named TDataStream. This name already avoids the collision with TStream which is basically what this class represents, but the Data prefix was not used to minimize the nomenclature collision, but rather because of its semantics.

But, let's say either Lazarus or Delphi have now implemented a general purpose class called... TDataStream.

Should I change the name of this class in all my projects that already use this nomenclature? Of course not!

I would only give the option for (new) developers to use another nomenclature for the same class, like this:

      TDataStream = class sealed(TInterfacedObject, IDataStream)
      // some methods
      TSuperDataStream = TDataStream;

The code now has 2 naming possibilities for the same class!

You can either continue to use TDataStream throughout your code, as long as you hold the declaration order of the units, or you can use the new TSuperDataStream option.

And, even if there is still a collision of nomenclature, it will be up to the developer to use the first technique of this article to solve it.

This way, both sides (package developers and package users) are free to use whatever names they want in their classes.

The only exception, as far as I know, are the components that are installed in the IDE. Both IDEs (Lazarus and Delphi) do not allow install components with the same name, even if they are in different packages.


The redeclaration or renaming of classes (and constants, functions, etc) is a technique that exists almost from the beginning of the language, but which the vast majority of developers do not use, either due to lack of knowledge, inability or lack of interest.

It is an old concept but it provides some techniques and possibilities for better encoding today, as demonstrated in this article.

See you.

Marcos Douglas B. Santos


This blog post has received 3 comments. Add a comment.

TMS RADical WEB, custom controls using HTML elements


Wednesday, March 14, 2018

In a previous blog, it was demonstrated how custom controls for TMS WEB Core could be created using the FNC framework and how easy it was to use FNC code that was written for VCL, FMX or LCL applications also for web applications.
In this article, we want to cover custom controls from an entirely different angle. That means custom controls that are built from a hierarchy of HTML elements. The example via which we want to explain building such custom controls is about a commonly used UI concept in web development, i.e. a login panel. This is where the user enters his username & password to authenticate. So, in this article, we’ll show the details how to make a TMS WEB Core custom UI control that represents such reusable login panel.

In terms of HTML, the login panel consists of an outer DIV element. In this DIV, we put different DIV elements to hold label text to indicate where the username HTML INPUT element is and where the password HTML input element is. Finally, there is a HTML BUTTON used to confirm the actual entry and do a login.

From a TMS WEB Core custom control perspective, this means we will create a Pascal class descending from TCustomControl and we’ll override the CreateElement virtual method. This CreateElement virtual method is what is responsible for returning the hierarchy of HTML elements representing the UI control. In this case, the code for CreateElement is:

function TLoginPanel.CreateElement: TJSElement;
  br: TJSElement;
  Result := document.createElement('SPAN');
  FCaption := TJSHTMLElement(document.createElement('DIV'));
  FUserInput := TJSHTMLElement(document.createElement('INPUT'));
  FPasswordInput := TJSHTMLElement(document.createElement('INPUT'));
  FUserLabelSpan := TJSHTMLElement(document.createElement('DIV'));
  FPasswordLabelSpan := TJSHTMLElement(document.createElement('DIV'));
  br := document.createElement('BR');
  FButton := TJSHTMLElement(document.createElement('BUTTON'));
  FButton.addEventListener('click', @HandleLoginClick);

Here FCaption, FUserLabelSpan, FUserInput, FPasswordLabelSpan, FPasswordInput and FButton are Pascal references to the HTML elements SPAN, INPUT and BUTTON used in the control. You can see that the HTML elements are created with document.createElement(). This is almost as if one would do this in Javascript, but via the Pascal to Javascript compiler, you can see the DOM object document can be used as Pascal object.

One more note about the CreateElement method, you can see that the purpose of the last line in the method: FButton.addEventListener('click', @HandleLoginClick);
is to attach a TLoginPanel method HandleLoginClick to the HTML element ‘onclick’ event handler. The HandleLoginClick method is declared as

function HandleLoginClick(Event: TJSMouseEvent): Boolean; virtual;

To see how this HTML element event handler is eventually hooked up to trigger the TLoginPanel.OnLogin event, see this code snippet from the TLoginPanel class:

TLoginPanel = class(TCustomControl)
    FOnLogin: TNotifyEvent;
    function HandleLoginClick(Event: TJSMouseEvent): Boolean; virtual;
   procedure DoLogin; virtual;
    property OnLogin: TNotifyEvent read FOnLogin write FOnLogin;

function TLoginPanel.HandleLoginClick(Event: TJSMouseEvent): Boolean;
procedure TLoginPanel.DoLogin;
  if Assigned(OnLogin) then

To interface our Pascal class with the HTML elements two more virtual method overrides are important. There is the UpdateElementVisual method and the UpdateElementData method. The purpose of the UpdateElementVisual method is to do changes to HTML element properties that affect the UI control visually. The UpdateElementData method is to do changes with respect to data contained in the HTML elements.
For this TLoginPanel control, we expose a few properties to set captions for labels in the UI control as well as the values of the HTML INPUT elements for username and password.

The properties of the TLoginPanel used for the login panel data are:

TLoginPanel = class(TCustomControl)
    property CaptionLabel: string read FCaptionLabel write SetCaptionLabel;
    property LoginLabel: string read FLoginLabel write FLoginLabel;
    property Password: string read GetPassword write SetPassword;
    property PasswordLabel: string read FPasswordLabel write SetPasswordLabel;
    property User: string read GetUser write SetUser;
    property UserLabel: string read FUserLabel write SetUserLabel;
The UpdateElementData method is:

procedure TLoginPanel.UpdateElementData;
  FUserLabelSpan.innerHTML := FUserLabel;
  FPasswordLabelSpan.innerHTML := FPasswordLabel;
  FButton.innerHTML := FLoginLabel;
  FCaption.innerHTML := FCaptionLabel;
  (FUserInput as TJSHTMLInputElement).value := FUser;
  (FPasswordInput as TJSHTMLInputElement).value := FPassword;

To customize the look and feel of the login panel, there is a dual approach. The first and classic Pascal like approach is the basic properties are defined to set the background & border color of the panel and the padding and margin of the controls within the panel. The second approach is that the CSS class can be set for the HTML elements used and that also via CSS full customization of look and feel is possible. This leaves the choice to either stick to classical Delphi style RAD development and do all from the form designer or work with separate CSS and perhaps leave the styling to a graphic designer instead of letting the developer do everything.

The visual properties exposed for the TLoginPanel class are:

TLoginPanel = class(TCustomControl)
    property BorderColor: TColor read FBorderColor write SetBorderColor;
    property Color: TColor read FColor write SetColor;
    property ElementClassName: string read FElementClassName write SetElementClassName;
    property ElementCaptionClassName: string read FElementCaptionClassName write SetElementCaptionClassName;
    property ElementInputClassName: string read FElementInputClassName write SetElementInputClassName;
    property ElementButtonClassName: string read FElementButtonClassName write SetElementButtonClassName;
    property ElementLabelClassName: string read FElementLabelClassName write SetElementLabelClassName;
    property Margin: integer read FMargin write SetMargin;
    property Padding: integer read FPadding write SetPadding;

The Element*ClassName properties allow to set CSS class names for the control itself (outer DIV), the SPAN elements for the labels associated with the INPUT elements and of course of also for the HTML INPUT and BUTTON elements.

The UpdateElementVisual override becomes:
procedure TLoginPanel.UpdateElementVisual;
  strpadding,strmargin: string;
  if Assigned(ElementHandle) then
    strpadding := IntToStr(Padding)+'px';
    strmargin := IntToStr(Margin)+'px';
    if (ElementClassName = '') then
      ElementHandle.style.setProperty('border', 'solid 1px '+ColorToHTML(BorderColor));
    if (ElementCaptionClassName = '') then
    if (ElementInputClassName = '') then
    if (ElementLabelClassName = '') then
    if (ElementButtonClassname = '') then

Notice how the properties are set when no CSS class is specified for HTML elements or otherwise the look and feel will be determined by the CSS.

The result becomes:

This is the basic look and feel without CSS.

Now, let’s bring bootstrap CSS and some custom CSS in the game and see the new look and feel.

Bootstrap is introduced with adding

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

to the project HTML file and in addition we add CSS in the project HTML file <STYLE> section to control the LoginPanel look & feel:
    .labelcenter {
      text-align: center;
    .loginpanel {
      padding: 15px;
    .loginpanelelement {
      margin-top: 2px;
      margin-bottom: 2px;
Now the appearance of the panel becomes:

We have only scratched the surface here of the many possibilities to enrich the TMS WEB Core framework with custom UI controls but we hope we have generated interest and enthusiasm.

Get started today: Technical previews of TMS WEB Core, TMS FNC UI web-enabled controls, web-enabled TMS XData, the first parts under the TMS RADical WEB umbrella are exclusively available now for all active TMS-ALL-ACCESS customers.

Bruno Fierens


This blog post has received 3 comments. Add a comment.

Previous  |  Next  |  Index