Stay in touch

Add your e-mail address here to receive the monthly TMS Software alerts.


Product releases
Product articles
Technical articles
Website changes
Software development


<< >>
April 2015

Saturday, April 25, 2015

TMS coming to Delphi events near you

I'm honored to have been invited to two upcoming first-class Delphi events in Europe (United Kingdom and Germany) in May. I'm looking forward to meet again with so many fellow Delphi developers and discuss topics that excite us all software developers in a changing world. The focus in my session will as-always be on utilizing TMS components to bring to your applications a maximum set of features with a minimum effort, in this case, to FireMonkey cross-platform applications targetting Windows, Android, iOS and Mac OS-X.
What I'm most thrilled about though is the opportunity to let you see a first glimpse of a major new component for FireMonkey under development in our labs for quite some time now. The upcoming events in May will be the very first and only place for now for this sneak preview. Ok, to keep a kind of nostalgic atmosphere of mystery, I say no more here :)

May 6,2015: Spring Delphi Community Conference: National History Museum London, UK

With 20 years of Delphi development, the UK Delphi community comes together on this major event with key speakers Marco Cantu, Jason Vokes, Pawel Glowacki and Stephen Ball covering all the power our beloved language Delphi brings in XE8, MVC / MVVM, multitier development, deployment via Google Play, FireDac ...
Places are limited, so rush to register now for this free event!

May 11-12, 2015: Delphi Developer Days: Holiday Inn Express, Frankfurt am Main, Germany

There is no match in technical depth and amount of information transfer for the Delphi Developer Days. Going strong for years, Delphi Developer Days means 2 days diving deep into all the power Delphi brings for developers. On the menu is modern VCL programming techniques, utilizing the latest database access technologies with FireDac, cross-platform FireMonkey development, REST services, ... in both break-out sessions and sessions in one room. As there is so much information that even 2 days is little to grasp everything, you go home with an over 300 pages conference book to catch-up at home later. This year, Delphi Developer Days is brought by Delphi guru, book writer, consultant, speaker Cary Jensen and Ray Konopka: Embarcadero MVP, winner of the coveted Spirit of Delphi award, and owner and founder of Raize Software, one of the first providers of third-party Delphi components. Places are extremely limited! More information and registration can be found at: Delphi Developer Days


Bruno Fierens

This blog post has not received any comments yet. Add a comment.

Friday, April 24, 2015

Audit Log using TMS Aurelius events

The latest release of TMS Aurelius introduces an event system that allows you to subscribe listeners to some events that might be fired while you are using Aurelius, especially the TObjectManager.

One key difference between regular Delphi events and Aurelius events is that the latter are multicast events, meaning you can add listeners (handlers) to the events without worrying if you are replacing a listener that was already set to event. This way it's possible to create "plugins" to Aurelius that perform additional logic. For example you can easily add code that will be executed whenever an entity is inserted in the database:
  procedure(Args: TInsertedArgs)
    // Use Args.Entity to retrieve the inserted entity

One very common use of that feature is implementing Audit Trail, a mechanism where you can log every entity (or database record, if you prefer to see it that way) that is created (inserted), deleted or modified (updated). The Music Library demo included in TMS Aurelius distribution was updated to include a simple Audit Log Viewer that illustrates how to use the events.

In the demo, the Audit Log Viewer just listen to the events and log them in a memo component in a form. You can enable/disable the logging. In real applications, you will just log the modifications to another place, like a text log file, or even the database itself, using Aurelius, if you prefer.

You can check the demo for the full source code. In this blog post, I will show only the relevant parts, for the OnInserted and OnUpdated events. Other parts of the code even inside procedures were removed for simplicity. Here is how we subscribe to the events:
  TfmAuditLogViewer = class(TForm)
    FInsertedProc: TInsertedProc;
    FUpdatedProc: TUpdatedProc;
    procedure InsertedHandler(Args: TInsertedArgs);
    procedure UpdatedHandler(Args: TUpdatedArgs);
    procedure SubscribeListeners;
    procedure UnsubscribeListeners;

constructor TfmAuditLogViewer.Create(AOwner: TComponent);
  FInsertedProc := InsertedHandler;
  FUpdatedProc := UpdatedHandler;

procedure TfmAuditLogViewer.SubscribeListeners;
  E: TManagerEvents;
  E := TMappingExplorer.Default.Events;

Note that we set the method reference in field variables so that we can later unsubscribe them if we want to:

procedure TfmAuditLogViewer.UnsubscribeListeners;
  E: TManagerEvents;
  E := TMappingExplorer.Default.Events;

And here is how we implemented our event listeners:

procedure TfmAuditLogViewer.InsertedHandler(Args: TInsertedArgs);
  Log(Format('Inserted: %s', [EntityDesc(Args.Entity, Args.Manager)]));

procedure TfmAuditLogViewer.UpdatedHandler(Args: TUpdatedArgs);
  Pair: TPair<string, Variant>;
  OldValue: Variant;
  Log(Format('Updated: %s', [EntityDesc(Args.Entity, Args.Manager)]));
  for Pair in Args.NewColumnValues do
    if not (Args.OldColumnValues.TryGetValue(Pair.Key, OldValue) and (OldValue = Pair.Value)) then
      Log(Format('   %s Changed from %s to %s',
        [Pair.Key, TUtils.VariantToString(OldValue), TUtils.VariantToString(Pair.Value)]));

Some different methods are called from those event handlers, but they are just helper methods. EntityDesc just retrieves a string representation of the entity being logged (class name and id), and Log and BreakLine just add text to the memo component:

function TfmAuditLogViewer.EntityDesc(Entity, Manager: TObject): string;
  IdValue: Variant;
  IdString: string;
  IdValue :=  TObjectManager(Manager).Explorer.GetIdValue(Entity);
  IdString := TUtils.VariantToString(IdValue);
  Result := Format('%s(%s)', [Entity.ClassName, IdString]);

procedure TfmAuditLogViewer.Log(const S: string);

procedure TfmAuditLogViewer.BreakLine;

After playing with Music Library demo for a while, adding and updating entities, we have our audit log results:


Wagner Landgraf

This blog post has not received any comments yet. Add a comment.

Friday, April 10, 2015

CORS and preflighted requests with TMS XData

From Wikipedia: Cross-origin resource sharing (CORS) is a mechanism that enables many resources (e.g. fonts, JavaScript, etc.) on a web page to be requested from another domain outside the domain from which the resource originated. In other words, if you are trying to access entities in a TMS XData server from a web page (for example, using AJAX requests), this mechanism (CORS) will eventually get in action.

Enabling CORS in TMS XData is very simple. Actually a single line of code:

    XDataServerModule.AccessControlAllowOrigin := '*';

And it will handle most of what’s needed, including preflighted requests. This post could end here if you are looking about how to deal with CORS in TMS XData. But let me use the opportunity to explain and illustrate how CORS works.

Let’s use the SQLiteConsolerServer demo that is included in TMS XData distribution. This very simple demo creates an SQLite database in memory, fill it with some predefined data (artists, albums and tracks), and starts a XData server in the address “http://localhost:2001/tms/music” to provide the objects from the database. This is what you will get after running the demo.

Now if we go to our browser (I’m using Chrome here) and enter the URL “http://localhost:2001/tms/music/Track(1)”, this is what we get:

So far so good, our server is providing the JSON representation of our Track object with id equals to 1. But what happens if we try to do the same request using AJAX? Let’s create a small HTML page with some JavaScript that performs the same GET request we’re doing directly with the browser. Here is the full code of the HTML:

<!DOCTYPE html>

	function processResponse(xmlhttp) {
		switch(xmlhttp.status) {
			case 200:
				var track = JSON.parse(xmlhttp.responseText);
			case 404:
				document.getElementById("getButton").innerText="(not found)";

	function getTrack1Name() { 
		var xmlhttp=new XMLHttpRequest(); 
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4) {

	function deleteTrack1() { 
		var xmlhttp=new XMLHttpRequest();"DELETE","http://localhost:2001/tms/music/Track(1)",true) 

<button onclick="getTrack1Name();" id="getButton">Get Track 1 Name</button> 
<button onclick="deleteTrack1();">Delete Track 1</button> 


Code is very simple, it just provides two buttons that perform GET and DELETE requests to get the name of Track 1 and delete Track 1, respectively.

Let’s open that page in browser (I’m using a WAMP server here but you could just double-click the HTML file):

If we click the first button to retrieve the name of Track 1, we get this:

It doesn’t work. Why is that? If we press F12 in Chrome to get more info about it, you can get a clue about what’s going on:

That’s CORS in action. The browser doesn’t allow a request from domain “localhost:8080” (where our web page is located) to the domain “localhost:2001” (where our XData server is located) unless our server states that it allows it (using the mentioned response header).

We can then modify our SQLiteConsoleServer demo to add that small line of code mentioned in the beginning of this post:

    Module.AccessControlAllowOrigin := '*'; // Add this line

Then if we restart our server, refresh our test page, and try pressing the button again, here is what we get:

Now it works! Here is the response returned by the XData server:

HTTP/1.1 200 OK
Content-Length: 228
Content-Type: application/json
Server: Microsoft-HTTPAPI/2.0
access-control-allow-origin: *
Date: Fri, 10 Apr 2015 14:08:03 GMT

    "$id": 1,
    "@xdata.type": "XData.Default.Track",
    "Id": 1,
    "Name": "Black Dog",
    "Composer": "Jimmy Page, Robert Plant, John Paul Jones",
    "Milliseconds": 296672,
    "Genre@xdata.ref": "Genre(1)"

Note the presence of header “access-control-allow-origin” which states that the server allows requests from any server. You could just restrict this to a specific server origin address by simply defining the name of the server instead of using “*” when setting the property.

Now what about preflighted requests? It will happen when we click our “Delete Track 1” button. From this nice Mozilla web page explaining CORS, it explains that a request must be preflighted if the HTTP method is different than GET, HEAD or POST, or even if request use custom headers or content-type different than some accepted ones. This covers a lot of very common REST requests: DELETE, PUT, or POSTing JSON data.

So what happens exactly when we click “Delete Track 1” button? This is the request Chrome will send to our XData server:

OPTIONS http://localhost:2001/tms/music/Track(1) HTTP/1.1
Host: localhost:2001
Connection: keep-alive
Access-Control-Request-Method: DELETE
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
Accept: */*
Referer: http://localhost:8080/tests/cors.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,pt;q=0.6

Note that instead of sending a DELETE request, it sent an OPTIONS request, which is the preflighted one. This means the browser is “checking” the server if the request he’s going to perform is valid. It indicates it’s going to perform a DELETE method using the “Access-Control-Request-Method” header. If the request had different headers, it would also send header “Access-Control-Request-Headers” to check with the server if the headers will be allowed.

The XData server then responds informing the client that the DELETE request will be accepted:

HTTP/1.1 200 OK
Server: Microsoft-HTTPAPI/2.0
access-control-allow-methods: GET,PUT,PATCH,DELETE,OPTIONS
access-control-allow-origin: *
access-control-max-age: 1728000
Date: Fri, 10 Apr 2015 14:16:15 GMT
Connection: close
Content-Length: 0

And finally, Chrome performs the actual DELETE request:

DELETE http://localhost:2001/tms/music/Track(1) HTTP/1.1
Host: localhost:2001
Connection: keep-alive
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
Accept: */*
Referer: http://localhost:8080/tests/cors.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,pt;q=0.6

If we press the “Get Track 1 Name” button again, we will be informed it doesn’t exist:

So, although enabling CORS in XData is just a single line of code, my intention here was to explain CORS with little more details, including preflighted requests, and show how XData makes it work under the hood.


Wagner Landgraf

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

Friday, March 27, 2015

Latest Additions to TMS Workflow

TMS products could be categorized by "application user-oriented" or "application developer-oriented".

Products like TMS Aurelius are heavily developer-oriented: it's the developer that "uses" the product during most of development time and the application user have no idea it even exists. Products like TAdvStringGrid stays in the middle: programming effort might be required to achieve the desired behavior of the grid, but at the same time the application user benefits from grid features, viewing data, interacting with the grid, etc..

TMS Workflow is in the "user-oriented" extreme: it's the application user, that will mostly use it. You will do the effort to do some initial setup, setup some configuration, build some initial integration, and that's it: now it's up to your user to benefit and live with TMS Workflow for the rest of your application life.

This post shows the new features introduced in most recent release of TMS Workflow, version 2.1. And you will see how it's heavily user-oriented and focused to make the life of application user - your customer! - easier.

Variables Tool Window
When building the workflow definition, there are many parameters that can use expressions and thus workflow or system variables. With this new helper tool, users can see all the existing variables that can be used in expressions, and can drag a variable name and drop in a control that accepts expressions to create an expression with the variable name. It's even useful to know which parameters can accept expressions.

Transition Scripts
TMS Workflow provides the Script Block to allow users to execute scripts when the workflow reaches a specified point in the diagram flowchart. But it's often not very productive to create the block, connect lines, etc.. And it also pollutes the workflow diagram with irrelevant information (most managers and workflow developers don't want to a script block in the middle of the process indicating that some low level stuff is being done).
To improve that TMS Workflow 2.1 introduces transition scripts: each transition can have a script associated to it. If the execution flow goes through a specified transition, the script associated with it (if any) is executed. Much easier to setup and makes your workflow diagram display only the relevant parts of the business process.

Send Mail Block
A block to send an e-mail notification. In addition to the automatic e-mail notification about tasks, if the user one to send a custom e-mail to someone, this is a very straightforward way to do it.

Database SQL Block
Executes an SQL statement in the database. Users don't need to create datasets or wait for you to build custom functions. If they just want to update some status in database or set some field value, that's an easy way to do so.

Status Templates
When creating tasks, users need to specify the list of valid status for the task. In many cases, the status are the same: "open/closed", or "pending/approved/rejected". Although TMS Workflow already has the Approval Task Block that automatically creates "approved/rejected" status, your user might have other needs for a different list of status, and still use it to create several different tasks. With status templates, you can predefine a list of status under a specified name, and your user can just choose from those predefined templates to quickly create the list of status in a task.

Comment and Text Blocks
Visual blocks to display information in the workflow diagram. The nice thing about text blocks is that they are dynamic, so your user can build a definition that shows a date, a task status, or other relevant information that changes as the workflow is being executed and tasks are being completed.


Wagner Landgraf

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

Previous  |  Next  |  Index

Copyright © 1995 - 2015 TMS Software v4.0

Being a fan of the Sakura replica watches Wars (Sakura Taisen) franchise, when I pop replica handbags this disc replica louis vuitton into my player, never in my life replica handbags was I so excited (well along with the rolex replica last volume of Princess Nine since louis vuitton replica it came with my order) to see this second OAV series. Can you tell what I think about this show?On chanel replica the first episode on the disc, it's chanel replica basically a story about Maria's past coming back to haunt her. A shady character, by the rolex replica name of Valentinov, comes to Japan looking to exact revenge rolex replica on Maria. He sets up bombs in the Imperial Theatre, which forces Maria breitling replica to come face with the past she wanted to leave behind.