Products



Stay in touch

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


    

Content:
Product releases
Product articles
Technical articles
Website changes
Software development

Products:

<< >>
May 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




Bookmarks: 

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:
TMappingExplorer.Default.Events.OnInserted.Subscribe(
  procedure(Args: TInsertedArgs)
  begin
    // Use Args.Entity to retrieve the inserted entity
  end
);

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)
  private
    FInsertedProc: TInsertedProc;
    FUpdatedProc: TUpdatedProc;
    procedure InsertedHandler(Args: TInsertedArgs);
    procedure UpdatedHandler(Args: TUpdatedArgs);
    procedure SubscribeListeners;
    procedure UnsubscribeListeners;
    {...}
  end;

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

procedure TfmAuditLogViewer.SubscribeListeners;
var
  E: TManagerEvents;
begin
  E := TMappingExplorer.Default.Events;
  E.OnInserted.Subscribe(FInsertedProc);
  E.OnUpdated.Subscribe(FUpdatedProc);
end;


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

procedure TfmAuditLogViewer.UnsubscribeListeners;
var
  E: TManagerEvents;
begin
  E := TMappingExplorer.Default.Events;
  E.OnInserted.Unsubscribe(FInsertedProc);
  E.OnUpdated.Unsubscribe(FUpdatedProc);
end;


And here is how we implemented our event listeners:

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

procedure TfmAuditLogViewer.UpdatedHandler(Args: TUpdatedArgs);
var
  Pair: TPair<string, Variant>;
  OldValue: Variant;
begin
  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)]));
  BreakLine;
end;


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;
var
  IdValue: Variant;
  IdString: string;
begin
  IdValue :=  TObjectManager(Manager).Explorer.GetIdValue(Entity);
  IdString := TUtils.VariantToString(IdValue);
  Result := Format('%s(%s)', [Entity.ClassName, IdString]);
end;

procedure TfmAuditLogViewer.Log(const S: string);
begin
  Memo.Lines.Add(S);
end;

procedure TfmAuditLogViewer.BreakLine;
begin
  Memo.Lines.Add('================================================');
end;


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





Bookmarks: 

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>
<html>
<body>

<script>
	function processResponse(xmlhttp) {
		switch(xmlhttp.status) {
			case 200:
				var track = JSON.parse(xmlhttp.responseText);
				document.getElementById("getButton").innerText=track.Name;
				break;
			case 404:
				document.getElementById("getButton").innerText="(not found)";
				break;
			default:
				document.getElementById("getButton").innerText="(invalid)";
		}
	}

	function getTrack1Name() { 
		var xmlhttp=new XMLHttpRequest(); 
		
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4) {
				processResponse(xmlhttp);
			}
		}
		xmlhttp.open("GET","http://localhost:2001/tms/music/Track(1)",true);
		xmlhttp.send(null); 
    }

	function deleteTrack1() { 
		var xmlhttp=new XMLHttpRequest(); 
		xmlhttp.open("DELETE","http://localhost:2001/tms/music/Track(1)",true) 
		xmlhttp.send(null); 
	}
</script>

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

</body>
</html>


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
    Server.AddModule(Module);
    Server.Start;
    {…}

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.



Bookmarks: 

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.




Bookmarks: 

Wagner Landgraf




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



Monday, March 23, 2015

From Lego to TComponent...

In my childhood, I was always fascinated by Lego. I spent countless hours building all kinds of things with Lego, many times a town, all kinds of houses but never forgetting a few sporty cars to accompany each house. What I could build with Lego was mostly limited by my imagination and the number of blocks I had (which was eventually quite a lot as I didn't let any anniversary or special celebration go by to ask for more Lego blocks as a gift). What is so nice about Lego is the easy to use interface between all kinds of blocks. All blocks fit on other blocks and can be effortlessly put together.

From my Lego time, fast-forward about 20 years, in 1995 Delphi brought us the concept of TComponent, an evolution of the TObject originated in Turbo Pascal. TComponent was an invention as magic as the Lego building block. While seamlessly interfacing with its parent, siblings, childs, the major difference with the basic Lego block is that a component can be heavily characterized through its properties. This makes 'playing' with components even more fun, interesting & challenging than playing with Lego was. At TMS software, it is always my objective to build interesting components that are as intuitive as possible to use and that could do interesting things while requiring to write a minimum amount of plumbing code to use them. With a wave of new components we released in the past quarter, I set a challenge for myself to explore and smoothen the ways to interface these building blocks to each other. In this article I wanted to highlight the easiness of bringing together quite sophisticated stand-alone components or building blocks to create powerful solutions.
To demonstrate this, we start with the versatile TMS TAdvStringGrid. With a few properties, this turns into an editable 2D data structure. In our sample, the grid is used to present or capture sales information for a 12 month period. The majority of the effort to write code for this sample goes into initializing the grid. In this case, all we still need to do in code is initialize the data contained in the grid:
var
  i: integer;
begin
  AdvStringGrid1.EditLink := AdvRichEditorEditLink1;
  AdvStringGrid1.Cells[1,1] := '';
  AdvStringGrid1.Cells[2,1] := 'Sales of development tools products per month';
  AdvStringGrid1.MergeCells(2,1,2,1);
  AdvStringGrid1.Cells[0,1] := 'Name';
  AdvStringGrid1.Cells[0,0] := 'Month';
  AdvStringGrid1.Cells[1,0] := 'Units';
  AdvStringGrid1.Cells[2,0] := 'Sales';
  AdvStringGrid1.Cells[3,0] := '% Market';

  for i := 2 to 13 do
  begin
    AdvStringGrid1.Cells[0,i] := FormatSettings.ShortMonthNames[i - 1];
    AdvStringGrid1.Ints[1,i] := Random(100);
    AdvStringGrid1.Ints[2,i] := Random(10000);
    AdvStringGrid1.Ints[3,i] := Random(50);
  end;
end;
All other settings are done by setting properties at design-time. Now, what we want to do is represent the information entered in the grid in a chart. The 3 columns of sales related data are represented in 3 series bar charts in TAdvChartView, the TMS chart component. With the easy interfaces of Lego in mind, we also want and can do this without writing much code (3 lines actually to be exact). We drop a TAdvChartView on the form and also 3 TAdvChartLink components. The TAdvChartLink is a component that takes on the interface between the grid data and a chart series data. The TAdvChartLink is so smart to update the chart series when the grid data changes through editing. All we need to do is setup the relationship between a specific range of cells and a series. Grid and chart are connected by assigning the grid to AdvChartLink.Grid and the chart to AdvChartLink.ChartView. What data from the grid is connected to what series is also configured at design-time via properties through the AdvChartLink.GridValues property. The chart itself can be configured at design-time with a special pane and series design-time editor that allows to customize the X-axis, Y-axis, colors, markers, ... everything about the chart actually. So, the only code left to write is the activation of the chartlink after the data is set in the grid:
  AdvChartLink1.Active := true;
  AdvChartLink2.Active := true;
  AdvChartLink3.Active := true;
Next step, we want to do is display the chart within an editable rich formatted document as we want to allow the user to create a customizable sales report that includes the chart. To do this, TAdvRichEditor is put on the form as well as a TAdvRichEditorEditToolBar and TAdvRichEditorFormatToolBar. We could have opted for a ribbon UI as well with TAdvRichEditorClipboardRibbonToolBar, TAdvRichEditorFontRibbonToolBar, TAdvRichEditorParagraphRibbonToolBar. TAdvRichEditorEditToolBar and TAdvRichEditorFormatToolBar or the ribbon toolbars are building blocks derived from our TMS Advanced ToolBars & Menus product that provides docking toolbars or ribbon controls. These 3 components together are already sufficient to have a rich formatted document editor without needing to write a single line of code.

All we do is initialize the content of the document a little bit with:
  AdvRichEditor1.InsertMultiLineText('Dear Mr. NAME'#13);
  AdvRichEditor1.SelStart := 9;
  AdvRichEditor1.SelLength := 4;
  AdvRichEditor1.SetSelectionMergeField('NAME');

  AdvRichEditor1.InsertMultiLineText('Included is the chart of sales for our development tool product.'#13#13);
  AdvRichEditor1.AddGraphic(500,400,'CHART');
With this initialization, we have inserted a merge field and a chart instance in the document. The merge field will be used to insert the addressee name with data from the grid.

The chart is added with initial dimensions 500 x 400px and with ID CHART. To have the chart displayed in the document, the event handler to draw custom graphic objects in the document needs to be coded:
procedure TForm1.AdvRichEditor1DrawGraphic(Sender: TObject; ACanvas: TCanvas;
  ARect: TRect; AID: string);
begin
  if AID = 'CHART' then
    AdvChartView1.PrintAllPanes(ACanvas, ARect);
end;
The merge of the addressee, NAME merge field in the document is done from the grid's OnCellValidate event for the cell 1,1 that holds the name. The code in this event handler is:
procedure TForm1.AdvStringGrid1CellValidate(Sender: TObject; ACol,
  ARow: Integer; var Value: string; var Valid: Boolean);
var
  sl: TStringList;
begin
  if (ACol = 1) and (ARow = 1) then
  begin
    AdvRichEditor1.UnMerge;
    sl := TStringList.Create;
    try
      sl.Values['NAME'] := Value;
      AdvRichEditor1.Merge(sl);
    finally
      sl.Free;
    end;
  end;
end;
But we are not satisfied yet. Wanting to generate a PDF document from the TAdvRichEditor content, a TAdvRichEditorPDFIO component is dropped on the form and connected to the TAdvRichEditor. All we need to do to get the PDF document from the TAdvRichEditor is add the code:
begin
  AdvRichEditorPDFIO1.Save;
end;
This will prompt for the filename to use and can optionally automatically display the generated PDF in the Windows default PDF viewer, again all without needing to write any additional code.


While not really functionally used in the sample, we just wanted to show yet another seamless integration between the TAdvRichEditor and TAdvStringGrid. In this case, the integration means we can use the TAdvRichEditor as inplace editor in the grid. And here comes yet another integration as this inplace TAdvRichEditor can optionally have a popup formatting toolbar for on-the-fly formatting of the editor content without the need for extra screen estate to put another toolbar or ribbon.


Get started by playing with these building blocks with the sample you can download here and the latest versions of TMS Component Pack and TMS Advanced Charts. We hope this article is an inspiration to have you create other powerful integrations. We're curious to see your results and hear about any suggestions you might have for new integrations or things that can make the integrations even smoother.

Bookmarks: 

Bruno Fierens




This blog post has received 1 comment. 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.