TMS to the rescue - Merging with TAdvRichEditor (guest article by Joe C. Hecht)

Bookmarks: 

Tuesday, August 07, 2018


A customer needed an application that could insert a “blog like” post into the middle of a corporate HTML page, making a sandwich of sorts between a header and footer template. Further, they needed the application to provide complete text formatting and image insertion. The app needed to be WYSIWYG (what you see is what you get), targeted to users with no experience at writing web code.

This application had to be very easy to use, quick to write, and I needed to deliver results — fast!

Writing a real time HTML editor and viewer is no walk in the park. I have a lot of experience at writing RTF editors and converting the results to PDF, and have enough experience to know that the Microsoft Windows® RTF control has exactly what I need to get the job done. Hooking up a Microsoft Windows® RichEdit to a pile of controls to set attributes will easily require a thousand lines of code, and even more code will be needed for conversion to HTML. Even with all the RTF related code I have archived back over the years, there was no way I could get the job out quickly using the Microsoft Windows® RichEdit control.

I purchased the TMS Component Pack from tmssoftware.com. The TMS Component Pack contains a massive collection of over 400 different VCL controls. Included is the TAdvRichEditor control, along with a wide range of associated controls used to enhance the job of RTF editing.

Using a TAdvRichEditor with one of the RTF Editing Toolbars, within a few minutes, I not only had a gorgeous interface to show off, I was able to easily output the RTF’s results as both a HTML and a PDF stream. So far so good!

TMS Software has a large number of controls specifically designed to make short work of setting the attributes of the text and other elements available in a TAdvRichEditor. TMS Software has tool bars, ribbon bars, popup menus, a host of “Office” like controls, and even a spell checker! The available attribute settings were amazing, including the ability to define lists, list types, add special characters and images, adjusting alignments and fonts, along with setting all the available text attributes, including subscript, superscript, and custom indents. They even have controls to support emoticons!

Leveraging the TMS Component Pack, I was able to quickly deliver several working mock-ups of the app, each with a different look and feel, and impress the customer with both an amazingly fast turnaround and lots of great looking choices! Did I mention how “quickly”? In minutes!

The customer loved it! They handed out the apps to the users to try out, asked the users to take notes, and then called back with a very short “wish list” — and a contract! Cha-Ching! Sale!!

The customer chose the TAdvRichEditorFormatToolBar for setting the RTF’s attributes, and the matching TAdvRichEditorEditToolBar for loading, saving, cut, copy, paste, undo, and re-do. Perfect!

Now for the fun part! The wish list. It seemed simple enough, but knowing the internals of RTF editing, I have the experience to know that often enough, simple requests may not always be so easy.


The “wish list”

Wish lists usually border on fantasies. TMS Software did a great job, since all the major up front work was done.

This list was short, sweet and realistic.

  • Add a “Color Button” to show the overall RTF’s background color.
  • Show only the “web like” fonts “Arial”, “Courier new”, and “New Times Roman”.
  • Override the “Load” and “Save” buttons to customize how they work.

Easy enough, right?


Plan of attack

Every wish list item requires access to the TMS toolbar child controls. A trap will be set in during the FormCreate() method that will obtain references to the needed TMS toolbar child controls at run time, remove the “non-web” fonts from the font combo box, and dynamically override the events for “Load” and “Save” to provide the requested changes.


Item: Add a “Color Button” to set and show the overall background color

Adding an extra button to the toolbar was super easy. Right clicking on the toolbar produced a long list of available controls to add. The TAdvOfficeColorSelector was the perfect choice for consistency, as it matches the other controls used by the TAdvRichEditorFormatToolBar to set color properties and also contains all the functionality required to display a drop down color selector. The “SelectColor()” event of the new button will be used to apply the background color to the RTF control.


Clicking the background color button

The background color of the RTF control is set via its “Color” property. The color is applied to the overall background when nothing is selected, SelectStart is -1, and SelLength is zero. When something is selected, or SelectStart is not -1, the color property will set either the background color at the caret, or the item that is selected.

To set background color, the “SelectColor()” event of the new button must:

  • Save the caret and current selection (if any) in the RTF control.
  • Make sure nothing is selected in the RTF control.
  • Set the RTF’s SelectStart to -1 and SelectLength to 0.
  • Set the background color.
  • Restore the caret and selection (if any).

Finally, the TAdvRichEditor’s DoSelectionChanged() method is called to synchronize the RTF control and the associated tool bar controls with the editing changes made by the new background color button.


Item: Show only the “web like” fonts “Arial”, “Courier new”, and “New Times Roman”

No objects or pointers are stored in the TAdvOfficeFontSelector items.objects array, so the list items for unneeded fonts are safely removed from the TAdvOfficeFontSelector (in reverse order).


Item: Override the “Load” and “Save” buttons to customize how they work

The OnClick() events for these buttons will be totally replaced, allow for custom processing.


Setting the trap

In the FormCreate() method, FindRTFEditContols() is called, where the child controls of the “TAdvRichEditorFormatToolBar” and “TAdvRichEditorEditToolBar” toolbars are identified, and saved back (as required). If the needed controls are not found, FindRTFEditContols() returns false, an error message is displayed, and the application is terminated.

A call is then made to TrapRTFEditContols() to actually set the traps, where the events for the “Load” and “Save” buttons are replaced, and the unneeded “non-web fonts” are safely removed from the TAdvOfficeFontSelector.


OpenFile — Normally, a dialog box is displayed, allowing the user to load the RTF from a number of different file formats. Per the customer’s requirements, the RTF is simply re-loaded from the same file every time (if it exists), otherwise default settings are used and no further processing is required.

The title for the web page is conveniently stored in the “Author” property of the RTF file, where it is extracted from the RTF file and used to set the “Text” property of the “WebpageTitle” edit control.

The background color of the RTF control is obtained by selecting “nothing” and retrieving the value of the “Color” property of the RTF control, then using the value retrieved to set the “SelectedColor” property of the AdvOfficeBkColorSelector control.

Finally, the caret is positioned in front of the first character in the RTF control (with no selection), and the AdvRichEditor’s DoSelectionChanged() method is called to synchronize the RTF control and the associated tool bar controls (containing the new background color button).


SaveFile — Normally, a dialog box is displayed, allowing the user to save the RTF in a number of formats. Per the customer’s requirements, the RTF’s contents are saved as a “.rtf” file always to the same name (for later recall), and the RTF’s contents are also saved as an HTML string to be merged with a corporate header and footer HTML template, producing a completed web page. The web page title is replaced in the “corporate header” with the contents of the “WebpageTitle” edit control, and the “Title” is saved as the “Author” property in the “.rtf” file (for later recall when the .rtf file is reloaded).

A “TAdvRichEditorMiniHTMLIO” component is used to covert the RTF’s contents to HTML. Setting the “PlainHTML” property causes no output of an HTML or body tag. An HTML DIV tag is used to surround the contents, using an inline style with the default font, size, and background color. Using a DIV tag allows additional style properties to be added (for example, additional margins and padding). Images used in the RTF are set to encode as “HTML inline images”, requiring no external images to be gathered for publishing. The end result is a single, self-contained web page that is ready to upload.


TAdvRichEditorProtectedAccess

TMS Software does a great job at making RTF easy. TMS Software wrote their own RTF controls, and have versions available for almost every platform on the planet! They have RTF controls for Delphi®, C++Builder®, and Lazarus, and available on the Linux®, OS X®, and Microsoft Windows® platforms. At the same time, TMS Software was very careful to implement a UI interface that closely follows the Microsoft Windows® RTF control (for UI consistency).

TMS Software uses brilliantly simple interfaces to wrap very complex controls. You can’t have everything. A simple interface sometimes means you may have to roll up your sleeves to get to a nitty-gritty internal.

In working with the internals of the TAdvRichEdit and associated controls, I found many of the protected methods and properties to be very useful. You may be thinking the same thing!

Adding custom functionality to a control often requires the need to write (and install) a descending class to access the protected methods and properties, however, simply declaring a descending class and casting an object reference works just as well (if nothing is added). This concept is known as a THack() class.

If I recall correctly, THack() was originally conceived in the early days of Delphi at Borland, by two bright young engineers I had the honor of working with: Andrew Kern and David Powell.

This concept is demonstrated by the use of the declared “TAdvRichEditorProtectedAccess” class, and is used to access the AdvRichEditor’s protected DoSelectionChanged() method.

Know that protected methods are not intended for use by the application. They are intended for component writers, and are internal to the object. You must be extremely careful in using them. Wherever possible, use public and published methods and properties as protected methods and properties are usually not documented, and the implementation (and even the existence) of these internals are subject to change at any time (and without warning).


Taking it further

The example presented demonstrates a great starting place for both the use and extension of only a few of the large number TMS Software’s RTF based controls available in the TMS Component Pack. Additional possibilities for use and enhancements of these controls are seemingly unlimited. In fact, I have a very excellent real time aware RTF edit popup menu that I plan to interface. In addition to the TMS Software RTF based controls, many other TMS controls come to mind that can be immediately (and easily) leveraged (such as the large number of TMS HTML based controls). TMS Software also provides for importing and exporting to and from many additional formats (such as PDF), support for many different image and vector formats, and also has database aware versions for most TMS controls.


Code Notes

HTML header and footer files used in the published example contain our own web code [plug].

The style used is typical of a Borland Pascal example, with function separator “comment” lines added that are similar to what is used by C++Builder® and what is displayed by default in the Lazarus IDE.

Three exceptions to the “Borland style” are used:

  • Code is always blocked using the rule “Never a then without a begin”.
  • Parentheses enclose every mathematical, logical, and bit-wise operation (for absolute clarity).
  • Function names are assigned rather than using the Delphi® automatic function “result” variable (allowing code to easily port to other compilers using a “native” code compile mode).

Most of my coding style comes from many years of experience, and a good read of Steve McConnell’s widely acclaimed books ‘Code Complete’ and ‘Code Complete 2’ ISBN 978-0-7356-1967-8 (2nd ed.)


Joe C. Hecht Author’s Lebenslauf

Joe C. Hecht is a software engineer that specializes in graphics engines, print drivers, PDF, SDK design, C++, and Delphi®.

Joe hails from Borland’s Delphi® group, where he earned the moniker “TJoe”.

His works include several award winning desktop publishing software titles including ‘TypeStudio’, ‘Effects Specialist’, and ‘OptiScript’.

Joe was a featured writer for the ‘Windows Tech Journal’, and went on to author many technical white papers during his time at Borland. He is acknowledged as a contributing author in many books that have been published on the subjects of computer graphics, programming, and Delphi®.

Joe participated in the 3D gaming engines used by Disney Interactive and 7th Level to produce many best-selling titles including Disney's ‘The Hunchback of Notre Dame’ and ‘Timon & Pumbaa's Jungle Games’, along with ‘Monty Python & the Quest for the Holy Grail’, ‘Monty Python's Complete Waste of Time’, ‘Ace Ventura’, ‘Battle Beast’, and Howie Mandel's ‘Adventures in Tuneland’.

Joe is the lead engineer for Code4Sale LLC and a long time Embarcadero Technology Partner. His current projects include the open source ÜberPDF™SDK, the GX Graphics™ high speed render engine, and cross platform development for Microsoft Windows®, OS X®, Linux®, Mobile, and Embedded.

When not coding, Joe can be found sailing the clear waters of Florida’s Emerald Coast aboard his sloop El deseo de padre.

Joe is actively soliciting quality code and other works in need of authorship and can be contacted at: https://code4sale.com/email/JoeHecht/


Save a tree

A PDF version of this article (including the complete source code listing) is available for download.


Project source code

The complete project's source files are available for download as a .zip file.

A source listing for the project's main unit is listed below.


The application

The Application



The HTML result
(With header and footer templates)

The HTML Result


The project's main unit

HtmlRTFMergerMainUnit.pas

unit HtmlRTFMergerMainUnit;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
interface
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
uses
//------------------------------------------------------------------------------
  Winapi.Windows,Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls, Vcl.ExtCtrls,
  //----------------------------------------------------------------------------
  ShellApi, // Needed to launch the system web browser
  AdvOfficeComboBox,  // Needed to acccess TMS toolbar comboboxes
  AdvRichEditorMiniHTMLIO, // Needed for specific HTML output
  //----------------------------------------------------------------------------
  AdvToolBar,
  AdvToolBarExt,
  AdvRichEditorToolBar,
  AdvRichEditorBase,
  AdvRichEditor,
  AdvScrollControl,
  AdvOfficeSelectors,
  AdvGlowButton;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
// Used to access the protected methods of TAdvRichEditor.
//------------------------------------------------------------------------------
type TAdvRichEditorProtectedAccess = class(TAdvRichEditor);
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
type
//------------------------------------------------------------------------------
  TForm1 = class(TForm)
    EditWebpageTitle: TEdit;
    AdvRichEditor: TAdvRichEditor;
    AdvRichEditorEditToolBar: TAdvRichEditorEditToolBar;
    AdvRichEditorFormatToolBar: TAdvRichEditorFormatToolBar;
    AdvOfficeBkColorSelector: TAdvOfficeColorSelector;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure AdvOfficeBkColorSelectorSelectColor(Sender: TObject;
                                                  AColor: TColor);
  protected
    RTFDefaultFontName: string;
    RTFDefaultFontSize: integer;
    TMSOpenFileButton: TAdvCustomGlowButton;
    TMSSaveFileButton: TAdvCustomGlowButton;
    TMSFontListComboBox: TAdvOfficeFontSelector;
    TMSFontSizeComboBox: TAdvOfficeFontSizeSelector;
    function FindRTFEditContols() : boolean;
    procedure TrapRTFEditContols();
    procedure NewTMSOpenFileButtonOnClick(Sender: TObject);
    procedure NewTMSSaveFileButtonOnClick(Sender: TObject);
  public
  end;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
var
  Form1: TForm1;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
implementation
//------------------------------------------------------------------------------
{$R *.dfm}
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
//------------------------------------------------------------------------------
begin
  // Set the default font and size for AdvRichEditor.
  RTFDefaultFontName := 'Arial';
  RTFDefaultFontSize := 12;
  AdvRichEditor.Font.Name := RTFDefaultFontName;
  AdvRichEditor.Font.Size := RTFDefaultFontSize;
  // Find the RTFEditContols
  if (not(FindRTFEditContols())) then begin
    ShowMessage('RTFEditContols not found - terminating');
    Application.Terminate();
  end;
  // Trap the RTFEditContols
  TrapRTFEditContols();
  // Open the RTF file (if it exists)
  NewTMSOpenFileButtonOnClick(self);
end;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
procedure TForm1.FormShow(Sender: TObject);
//------------------------------------------------------------------------------
begin
  AdvRichEditor.SetFocus();
  AdvRichEditor.SelectText(0, 0);
end;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
function TForm1.FindRTFEditContols() : boolean;
//------------------------------------------------------------------------------
var
  idx: integer;
  aControl: TControl;
begin
  // Get a reference to the open and save buttons in AdvRichEditorEditToolBar
  TMSOpenFileButton := AdvRichEditorEditToolBar.GetButton(integer(btFileOpen));
  TMSSaveFileButton := AdvRichEditorEditToolBar.GetButton(integer(btFileSave));
  // Get a reference to the font ComboBoxes in AdvRichEditorEditToolBar
  for idx := 0 to (AdvRichEditorFormatToolBar.ControlCount - 1) do begin
    if ((Assigned(TMSFontListComboBox)) and
        (Assigned(TMSFontSizeComboBox))) then begin
      break;
    end;
    aControl := AdvRichEditorFormatToolBar.Controls[idx];
    if (aControl is TAdvOfficeFontSelector) then begin
      TMSFontListComboBox := TAdvOfficeFontSelector(aControl);
      continue;
    end;
    if (aControl is TAdvOfficeFontSizeSelector) then begin
      TMSFontSizeComboBox := TAdvOfficeFontSizeSelector(aControl);
      continue;
    end;
  end;
  // Make sure all references are valid
  if (not((Assigned(TMSOpenFileButton)) and
          (Assigned(TMSSaveFileButton)) and
          (Assigned(TMSFontListComboBox)) and
          (Assigned(TMSFontSizeComboBox)))) then begin
    FindRTFEditContols := false;
    exit;
  end;
  FindRTFEditContols := true;
end;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
procedure TForm1.TrapRTFEditContols();
//------------------------------------------------------------------------------
var
  idx: integer;
  fontName : string;
begin
  // Set the new OnClick events for the OpenFile and SaveFile buttons
  TMSOpenFileButton.OnClick := NewTMSOpenFileButtonOnClick;
  TMSSaveFileButton.OnClick := NewTMSSaveFileButtonOnClick;
  // Safely remove all non web compatible fonts from the FontListComboBox
  for idx := (TMSFontListComboBox.Items.Count - 1) downto 0 do begin
    fontName := TMSFontListComboBox.Items[idx];
    if ((fontName <> 'Arial') and
        (fontName <> 'Courier New') and
        (fontName <> 'Times New Roman') and
        (not(Assigned(TMSFontListComboBox.Items.Objects[idx])))) then begin
      TMSFontListComboBox.Items.Delete(idx);
    end;
  end;
end;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
procedure TForm1.AdvOfficeBkColorSelectorSelectColor(Sender: TObject;
                                                     AColor: TColor);
//------------------------------------------------------------------------------
var
  selection : TSelection;
  caret : TCaret;
begin
  AdvRichEditor.SetFocus();
  // Save the selection (if any)
  selection := TSelection.Create();
  selection.Assign(AdvRichEditor.Selection);
  // Save the caret
  caret := TCaret.Create();
  caret.Assign(AdvRichEditor.Caret);
  // Select nothing and set SelStart to -1 to set the RTF's background color
  AdvRichEditor.UnSelect();
  AdvRichEditor.SelStart := -1;
  AdvRichEditor.SelLength := 0;
  // Set RTF's background color
  AdvRichEditor.Color := AColor;
  // Restore the selection (if any)
  AdvRichEditor.Selection.Assign(selection);
  selection.Free();
  // Restore the caret
  AdvRichEditor.Caret.Assign(caret);
  caret.Free();
  // Update AdvRichEditor and the toolbar
  TAdvRichEditorProtectedAccess(AdvRichEditor).DoSelectionChanged();
end;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
procedure TForm1.NewTMSOpenFileButtonOnClick(Sender: TObject);
//------------------------------------------------------------------------------
begin
  if (not(FileExists('__AdvRichEditContents.rtf'))) then begin
    exit;
  end;
  AdvRichEditor.LoadFromFile('__AdvRichEditContents.rtf');
  // The web-page title is stored in the AdvRichEditor.Author property
  EditWebpageTitle.Text := Trim(AdvRichEditor.Author);
  // Select nothing and set SelStart to -1 to get the RTF's background color
  AdvRichEditor.UnSelect();
  AdvRichEditor.SelStart := -1;
  AdvRichEditor.SelLength := 0;
  AdvOfficeBkColorSelector.SelectedColor := AdvRichEditor.Color;
  // Set the caret to the first text char (if any - with no selction)
  AdvRichEditor.SelectText(0, 0);
  // Update AdvRichEditor and the toolbar
  TAdvRichEditorProtectedAccess(AdvRichEditor).DoSelectionChanged();
end;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
procedure TForm1.NewTMSSaveFileButtonOnClick(Sender: TObject);
//------------------------------------------------------------------------------
var
  htmlSL: TStringList;
  htmlOutText: string;
  uppercaseTestString: string;
  posStart: integer;
  posEnd: integer;
  advRichEditorMiniHTMLIO: TadvRichEditorMiniHTMLIO;
  rtfContentsAsHtml: string;
  backgroundRGBColor: DWORD;
begin
  //----------------------------------------------------------------------------
  // Produce an HTML page by merging:
  // _htmlBegin.txt + rtfContentsAsHtml + _htmlEnd.txt
  // Replace '<title>Title</title>' in _htmlBegin.txt with EditTitle.Text.
  // Replace ' ' in rtfContentsAsHtml with spaces.
  // Add <DIV> tag of RTFBackgroundColor and default Font to rtfContentsAsHtml.
  // Save with Unix line endings, encoded in UTF-8
  //----------------------------------------------------------------------------
  // Create a stringlist to load and save html code with
  htmlSL := TStringList.Create();
  // Load the HTML Header text
  htmlSL.LoadFromFile('_htmlBegin.txt');
  htmlOutText := htmlSL.Text;
  // Make an uppercase version of htmlOutText to search for the title tag
  uppercaseTestString := UpperCase(htmlOutText);
  // Search for the title tag and replace it (if found)
  posStart := pos('<TITLE>', uppercaseTestString);
  if (posStart > 0) then begin
    posEnd := pos('</TITLE>', uppercaseTestString);
    if (posEnd > 0) then begin
      // delete and insert to htmlOutText
      delete(htmlOutText,
             posStart,
             (posEnd - posStart));
      insert('<title>' + Trim(EditWebpageTitle.Text), htmlOutText, posStart);
    end;
  end;
  // Create a advRichEditorMiniHTMLIO to get the RTF contents as HTML
  advRichEditorMiniHTMLIO := TadvRichEditorMiniHTMLIO.Create(nil);
  advRichEditorMiniHTMLIO.RichEditor := AdvRichEditor;
  advRichEditorMiniHTMLIO.PlainHTML := true;
  advRichEditorMiniHTMLIO.InlineImages := true;
  // Get the RTF's HTML text
  rtfContentsAsHtml := advRichEditorMiniHTMLIO.AsString();
  // Replace '&nbsp;' with spaces.
  posStart := pos('&nbsp;', rtfContentsAsHtml);
  while (posStart > 0) do begin
    delete(rtfContentsAsHtml, posStart, length('&nbsp;'));
    insert(#32, rtfContentsAsHtml, posStart);
    posStart := pos('&nbsp;', rtfContentsAsHtml);
  end;
  // Add rtfContentsAsHtml to htmlOutText, adding a <DIV> tag around the
  // rtfContentsAsHtml with the background color, default font, and font size.
  backgroundRGBColor := ColorToRGB(AdvOfficeBkColorSelector.SelectedColor);
  htmlOutText := htmlOutText +
                 '<DIV style="background-color:#' +
                 IntToHex(GetRValue(backgroundRGBColor), 2) +
                 IntToHex(GetGValue(backgroundRGBColor), 2) +
                 IntToHex(GetBValue(backgroundRGBColor), 2) +
                 '; font-family: ' +
                 RTFDefaultFontName +
                 '; font-size:' +
                 IntToStr(RTFDefaultFontSize) +
                 'pt;">' +
                 rtfContentsAsHtml +
                 '</DIV>';
  // Load the HTML Footer text
  htmlSL.LoadFromFile('_htmlEnd.txt');
  // Add the HTML Footer text to the htmlOutText
  htmlOutText := htmlOutText + htmlSL.Text;
  // Save the htmlOutText with Unix line endings, encoded in UTF8.
  htmlSL.Text := htmlOutText;
  htmlSL.LineBreak := #10;
  htmlSL.SaveToFile('_htmlOut.htm', TEncoding.UTF8);
  htmlSL.Free();
  // Save the WebpageTitle in AdvRichEditor.Author
  AdvRichEditor.Author := Trim(EditWebpageTitle.Text);
  // Save the RTF
  AdvRichEditor.SaveToFile('__AdvRichEditContents.rtf');
  // Open the resulting HTML file in the default browser
  ShellExecuteW(self.Handle,
                'open',
                PWChar(ExpandFileName('_htmlOut.htm')),
                nil,
                nil,
                SW_SHOWNORMAL);
end;
//------------------------------------------------------------------------------



//------------------------------------------------------------------------------
end.


Copyright © 2018 by Joe C. Hecht - All rights reserved

Thanks Joe for a great article!

Bruno Fierens


Bookmarks: 

This blog post has received 1 comment. Add a comment.



TMS WEB Core v1.0 Brescia tips & tricks

Bookmarks: 

Friday, August 03, 2018



Since several months, many enthusiast Delphi developers that are on our TMS ALL-ACCESS subscription are exploring the new TMS WEB Core.
Since little over a week ago, we officially released the first version v1.0 named "Brescia edition" to symbolize the start of a new journey "Mille Miglia" and many more Delphi developers already joined us on this journey. From the numerous feedback we received so far, the two most commonly heard comments are:

"The more I work with TMS WEB Core, the more I love it"

and

"Wow, the possibilities of TMS WEB Core are virtually unlimited"

With so much possibilities comes a lot of knowledge. While our team also spends a significant amount of time on writing documentation and demos, we want to give in addition to this tips & tricks based on your questions at regular intervals. So, let's kick of with looking at two tips in depth:

Starting a TMS WEB Core application at a dynamically selected form

The default behavior is that a TMS WEB Core application starts with presenting the main form of the application, pretty much as is default the case for Delphi VCL applications. The code responsible for this is:
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
Well, that's exactly the same as for a Delphi VCL application I can see you think, well, it is and it is on purpose to make all Delphi developers feel right at home with TMS WEB Core. When we start the TMS WEB Core application from its URL, for example, http://localhost:8000/Project1/Project1.html, this code will be executed and TForm1 will be created and shown in the browser. Then typically, from this main form, often many sub, detail or just other forms are being displayed during the lifetime of the application. Suppose that we have a very traditional customers/products/orders application. Wouldn't it be nice to create an URL that would allow us to directly navigate from our browser to the main sales form?

Well, doing this is actually quite simple. To start the application with the wanted form, we'll use a request parameter 'form' and have 3 keywords: customers, products and sales. We have forms TCustomersForm, TProductsForm, TOrdersForm. Let's use the request parameter now to start the form we want:
begin
  Application.Initialize;
  Application.AutoFormRoute := true;
  Application.MainFormOnTaskbar := True;

  if HasQueryParam('form',s) then
  begin
    if CompareText(s, 'customers') = 0 then
      Application.CreateForm(TCustomersForm1, CustomersForm);

    if CompareText(s, 'products') = 0 then
      Application.CreateForm(TProductsForm1, ProductsForm);

    if CompareText(s, 'sales') = 0 then
      Application.CreateForm(TSalesForm1, SalesForm);
  end
  else
    Application.CreateForm(TMainForm, MainForm);

  Application.Run;
end.
With this code in place, when no request parameters are used, i.e. http://localhost:8000/Project1/Project1.html the default TMainForm is shown. When we start the application via the URL http://localhost:8000/Project1/Project1.html?form=sales, it will show the sales form directly.

Important note is that the function HasQueryParam() is a function working with URL request parameters found in the unit WEBLib.WebTools, so make sure to add this unit to the uses list when you want to use it.

Creating forms fully dynamical with components

By default, every form in a TMS WEB Core application has a HTML file associated with it and the default behavior is that when opening a new form, this form uses the entire browser window. TMS WEB Core also facilitates to show forms created with the designer as a popup form or host this form in a HTML element on the form. But in this tips & tricks blog, we want to show how you can also create a form fully in code and display it. Surprisingly, or rather unsurprisingly, doing this is almost identical to dynamically creating a form with controls in VCL.

For the purpose of this example, we will create a form that will host a TWebMemo component and via this TWebMemo component, we will edit the contents of a TWebListBox on the form.
The form will contain a bottom aligned panel with an OK and Cancel button and a client memo control.

Let's jump into the code immediately:

procedure TForm1.WebButton1Click(Sender: TObject);
var
  btnOK,btnCancel: TWebButton;
  mem: TWebMemo;
  pnl: TWebPanel;
  frm: TWebForm;

  procedure HandleDialogClose(AValue: TModalResult);
  begin
    if AValue = mrOK then
    begin
      WebListBox1.Items.Assign(mem.Lines);
    end;
  end;

begin
  frm := TWebForm.CreateNew(self);
  frm.Caption := 'Strings';
  frm.Width := 600;
  frm.Height := 400;
  frm.Color := clWhite;
  frm.Shadow := true;
  frm.Position := poScreenCenter;
  frm.Border := fbSizeable;
  frm.Top := 100;
  frm.Left := 100;

  mem := TWebMemo.Create(frm);
  mem.Parent := frm;
  mem.AlignWithMargins := true;
  mem.Align := alClient;
  mem.Margins.Left := 10;
  mem.Margins.Right := 10;
  mem.Margins.Top := 10;
  mem.Margins.Bottom := 10;
  mem.ElementClassName := 'form-control';
  mem.Lines.Assign(WebListBox1.Items);

  pnl := TWebPanel.Create(frm);
  pnl.Parent := frm;
  pnl.Height := 40;
  pnl.Align := alBottom;

  btnOK := TWebButton.Create(pnl);
  btnOK.Parent := pnl;
  btnOK.Caption := 'OK';
  btnOK.Left := 400;
  btnOK.Top := 5;
  btnOK.Width := 90;
  btnOK.Height := 30;
  btnOK.Tag := mrOK;
  btnOK.ElementClassName := 'btn-primary';
  btnOK.OnClick := BtnClick;
  btnOK.Hint := 'Accept changes';
  btnOK.ShowHint := true;
  btnOK.Anchors := [akRight, akTop];

  btnCancel := TWebButton.Create(pnl);
  btnCancel.Parent := pnl;
  btnCancel.Caption := 'Cancel';
  btnCancel.Left := 500;
  btnCancel.Top := 5;
  btnCancel.Tag := mrCancel;
  btnCancel.Width := 90;
  btnCancel.Height := 30;
  btnCancel.OnClick := BtnClick;
  btnCancel.ElementClassName := 'btn';
  btnCancel.Hint := 'Exit dialog';
  btnCancel.ShowHint := true;
  btnCancel.Anchors := [akRight, akTop];

  if Assigned(frm.CaptionElement) then
  begin
    frm.CaptionElement.style.setProperty('background-color','#009FE4');
    frm.CaptionElement.style.setProperty('font-size','14pt');
  end;

  frm.ShowModal(@HandleDialogClose);
end;
What you see here is quite straightforward and almost 100% VCL like. A form is created, its size and position is set and then the various controls that should be hosted in the form are created. Noteworthy is that for the buttons an ElementClassName is set. When we have added Bootstrap support, we can use the Bootstrap 'btn' & 'btn-primary' class names for the buttons and 'form-control' for the memo. To add Bootstrap support, simply select from the project context menu in the IDE project manager "Manage JavaScript Libraries" and select Bootstrap. You can see that the buttons are also right-anchored, indicating the align and anchoring paradigm of the VCL is avaible for those Delphi developers wanting to use it. Notice also the access to the caption element. This is the HTML element (a span) that represents the caption of the form. By having access to the HTML caption element, this means we can fully use CSS to style it. Here just a simple background color and font size is applied but nothing would prevent you from further customization and even inserting extra HTML child elements in this caption.

The form is at last displayed with the ShowModal() method. Here is an important difference. In the browser, there are no blocking calls! It would be a very bad idea to have blocking methods in the browser. As we do not have the concept of blocking calls, we need an alternative way to capture the result of closing the "modal" form and that is by using a procedure as parameter. This procedure will be called when the form closes and will return the "modal" result set by the buttons on the form. From this procedure, we can get the contents of the TWebMemo on this form and assign these to the TWebListbox on the main form.

Finally, we also had to add some code to handle the button click to set the modal result and close the form. This is done by assigning the button.OnClick event handler and this event handler code is:

procedure TForm1.BtnClick(Sender: TObject);
var
  lForm: TWebForm;
begin
  lForm := TWebForm((Sender as TWebButton).Parent.Parent);
  lForm.ModalResult := (Sender as TWebButton).Tag;
  lForm.Close;
  lForm.Free;
end;
As you can see, we get access to the form by getting the parent of the TWebPanel that is the parent of the buttons. With the access to this form object, we can set the ModalResult and close it.

The end result looks like:


We hope these first tips are useful and we wish you a further enjoyable journey with TMS WEB Core. Our team loves your challenges, so get in touch and we look forward to assist and create from this more tips & tricks for TMS WEB Core, TMS XData or TMS FNC controls for creating modern web applications.

Bruno Fierens


Bookmarks: 

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



From Database to Web App through REST server with TMS XData and TMS WEB Core

Bookmarks: 

Monday, July 30, 2018



As you might already know, TMS WEB Core v1.0 Brescia edition has been released. A game changer in web development using Delphi IDE. From all the "magic" things it does, one of my preferred is this: the way you code with it feels very very home for Delphi users - the language, the IDE, the form designer, the object properties, the component library... Still, what it produces is totally new and different. It creates html/js Simple Page Applications! It wraps real, existing JavaScript frameworks and controls. It connects to REST servers. It's a modern architecture that is far away from what many of Delphi developers are used to..

And that is probably the reason why we are receiving this frequent question: "How do I connect to my [MySql] database from TMS Web Core?" (and you can replace [MySql] by your favorite database server).

The answer is: You have plenty of options, but the best one is: TMS XData.

Why TMS XData, even for non-TMS Web users?

TMS XData is REST/JSON server framework. You can relate it more or less with DataSnap, ASP.NET Core Web Api, NodeJS Loopback, etc. But for Delphi. And how TMS XData is different? It's hard to say in a few words, but I will try by saying this: it just works: the product, the support and the experience.

XData is stable. It's incredible how low support we have from people complaining about errors, weird behavior, bad performance, etc. Recently, one pleased XData user sent to us a screenshot of his error log report. Take a look:



The first one was the number of errors logged from using DataSnap. After so many frustrations, he migrated to another one. The second line are errors from that second middle-tier Delphi framework he used. Then third one were errors after migrating to TMS XData. Zero errors. He even did the work to check if the error logging was working by forcing an error to happen, because he was not believing it. "Finally!" was his relief words.

Also other benchmark tests were done regarding memory usage, errors, performance. This one was made by a customer who was evaluating options at that time - not a XData user yet when the test was done: https://datasnapperformance.wordpress.com. He decided to go for TMS XData after being convinced by its own tests.

XData is a pleasure to work with. It doesn't have a steep learning curve. Documentation is extensive and using it is very intuitive. You can try it yourself. You can ask XData users. And there is TMS Aurelius behind it you can optionally use. The state-of-the-art ORM framework for Delphi. As a quick example, this is a code snippet of what you can to write to hit your database, retrieve a list of customer from your database based on a specific criteria, convert them to object instances, and return it as JSON from your server:
function ICustomerService.CustomersByStatus(Status: TCustomerStatus): TList<TCustomer>;
begin
  Result := TXDataOperationContext.Current.GetManager
    .Find<TCustomer>
    .Where(Linq['Status'] = Status)
    .OrderBy('Name')
    .List;
end;
TMS XData and TMS Web Core

Being TMS Web Core a SPA application, you can't connect to a database directly from the web application. You need a middle tier that will be the bridge between the web app and database. You should create one like everyone else, using PHP, ASP.NET, NodeJS, Delphi. But with TMS XData, it's an order of magnitude easier. You don't even need to write code like the CustomersByStatus method above if you don't want to - XData does it all automatically for you.

Import database structure and create classes. Using the TMS Data Modeler tool - included together with XData - you can import your database structure and then create a unit with all TMS Aurelius classes representing the database and then, export it as a unit with Aurelius classes:



Create XData server and add the ORM entities

Now using the TMS XData Server wizard, you can create the XData server in a few clicks, and just add the unit generated by Data Modeler to the project. Your server should be up and running!



Web front end using Dataset-like approach From TMS Web Core usage, XData provides high-level RAD framework. First you have TXDataWebConnection, which you just need to configure the root URL of the server. Then assign a TXDataWebClient or TXDataWebDataset to the connection, and you can retrieve/save data with simple methods. By using the dataset, you can even go further and bind to data-aware controls using the datasource. In the example below, we have a TWebDBTableControl bound to the datasource, bound to XData dataset, bound to XData connection:



And this is what we get in the browser. Note how data was retrieved from the server: an HTTP request to our REST server, and JSON format being return. Yes, a real pure html/js web application with REST Api backend, and we code it the Delphi way!



And that's how you get your data in the web! Of course, from now on, the sky is limit. In the backend, you can add complex business logic, authentication and authorization, and all the takes to create a real application. For the frontend, you have the plenty of TMS Web Core controls available, and all the html, javascript and css power in your hands to make your UI rich, nice and responsive.

Here is a screenshot of our TMS XData Music Web Application demo, available online at https://app.devgems.com/xdata/music/app. Full source code of the demo is available when you install TMS XData.



Do you want to see all the process described in the blog post in action? Watch the video below!



Learn more about TMS XData:
and exploring the trial versions downloads available for Delphi.

Wagner R. Landgraf


Bookmarks: 

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



A new world opens for FNC: the world-wide-web!

Bookmarks: 

Thursday, July 26, 2018



Yesterday, we released TMS WEB Core v1.0 Brescia edition. This brings RAD component based development of web client applications from the Delphi IDE. TMS WEB Core is designed to be open. It comes with numerous built-in UI controls that are close equivalents to standard VCL controls like TButton, TListBox, TEdit, TMemo, TStringGrid etc... In addition, TMS WEB Core comes with Pascal wrapper classes that allow you to use jQuery controls (we have a partnership with jQWidgets for this) and in the future, we'll bring much more wrappers around existing web controls. But for Delphi developers, there is an extra gem, the TMS FNC Controls.

It all started end of 2015 ...

We started developing the FNC architecture in 2015 as a UI control architecture that would allow to create UI controls from a single source code base that are usable from VCL Windows applications, FMX Windows, macOS, iOS and Android applications and also from LCL (the component framework in the free Lazarus IDE) for Windows, macOS and Linux.

With the FNC architecture, there is only one learning curve to master complex UI controls like a grid, planner, treeview, richeditor, ... in different applications using different frameworks, but you can also reuse your UI control logic through these different applications.

Opening a new world for FNC ...

Now, with TMS WEB Core, we have opened a whole new world for the FNC controls. Yes, we have web-enabled the FNC control architecture and that means that, from now on, you can use our FNC UI controls from TMS WEB Core web client applications.

FNC in action

To see the power of the TMS FNC controls, here you can find the FNC grid in the Delphi IDE used in a TMS WEB Core application:

and it runs directly in the browser.



Or even better, just play with the demo yourself from your browser here

At this moment, the TMS FNC Chart, TMS FNC UI Pack and TMS FNC Dashboard Pack components will also show on the toolpalette and are ready for use in TMS WEB Core client applications. The FNC architecture is an open architecture. This means that you can also use it to create your own custom UI controls. You do this by descending from TTMSFNCCustomControl and by doing so, you will have a UI control that you will be able to literally use everywhere, from a Windows application to a Raspbian application, from an iOS app to a web application.

Learn more about TMS FNC controls via our and exploring the trial versions downloads available for Delphi & C++Builder.

Bruno Fierens


Bookmarks: 

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



TMS WEB Core v1.0 Brescia is released, the journey begins

Bookmarks: 

Wednesday, July 25, 2018



Several months ago, we announced the TMS RADical Web concept to bring RAD component based web development to Delphi. The foundation of this concept is TMS WEB Core.

TMS WEB Core comprises:
  • a component framework
  • a (Web) RTL
  • a Pascal to JavaScript compiler
  • a Delphi IDE integration

TMS WEB Core enables to build modern web client applications following the single-page architecture that also other modern frameworks like Angular, vue.js, React employ.

The web for Delphi developers ...

With TMS WEB Core, you feel right at home in the Delphi IDE, not only with respect to language, code editor, form designer but also with respect to component framework where many direct web-based counterparts are available for edit, button, label, listbox, grid ... and many more UI controls.

Delphi for web developers ...

Equally important, with TMS WEB Core, you also feel right at home in the magic world of web development. TMS WEB Core is designed to be open to other Javascript frameworks. Out of the box, there is support to bind to jQuery controls and we have wrapped already a significant part of the jQWidget UI controls into easy to use Pascal components. TMS WEB Core is totally open to CSS styling, to optionally design web pages fully in HTML/CSS (possibly by a person different from the software developer with expertise in nice graphic webdesign).
TMS WEB Core is also to the server technology of your choice. TMS WEB Core web client applications can enjoy first-class binding to data via the familiar TDataSet concept via our TMS XData REST API server technology. But you can use at the same time Embarcadero RAD Server, a node.js based REST API, ASP.NET Core microservices etc..

The power of FNC ...

And we did more magic with TMS WEB Core and the TMS FNC framework. We did nothing less than web-enable our FNC component architecture. That means that our FNC UI controls that were already usable for VCL Windows application development, FMX Windows, macOS, iOS and Android development and LCL Windows, macOS and Linux/Raspbian development can now also be used to develop web applications. This incredible technical wizardry has never been done before. Now you can share Delphi code between desktop, mobile and web applications, including user-interface logic as our FNC controls are 100% code interface compatible between VCL, FMX, LCL and now also WEB with TMS WEB Core.

We invite you to take your time to explore the tons of new capabilties that open up for Delphi developers. A fully functional trial for TMS WEB Core is available now as well as trial versions for TMS FNC UI controls, TMS XData and you can use of course TMS WEB Core with other JavaScript libraries, other web controls and other server technologies. There is meanwhile a long list of videos showing the features and capabilities

A huge team effort ...

The development of TMS WEB Core took almost 1.5 years so far with a team of over 10 people and was the biggest effort ever in the history of our company TMS software. Never before have so many talented colleagues and bright minds been at work together on this exciting new development. Never before have we been so passionate about a new product. Discover more about the team wizards in upcoming blogs. Now it is your time to enjoy the fruits of this hard labor.

Low barrier to start ...

We wanted TMS WEB Core to be easily accessible & affordable for everyone. From hobbyist, self-employed consultants, small companies, schools, trainers to large companies. Therefore, we have set the TMS WEB Core launch price to only 295EUR for a single developer license. You will not only enjoy this new exciting product, but also get updates & priority support for a full year. After this period, renewals are offered at 70% discount of the new license price.
For developers who like to have the convenience of having our full toolbox directly accessible, there is TMS ALL-ACCESS. With TMS ALL-ACCESS you get TMS WEB Core to build your web client applications but also our TMS XData REST API server technology and our full suite of FNC UI controls to enrich your array of UI controls available for your web applications.

Only the beginning ...

The world of the web is large, very large. With TMS WEB Core v1.0, we are only at the beginning of a long journey. As the famous Mille Miglia route is one of the most fantastic journeys in the world, we thought it was nice to visualize the journey of TMS WEB Core as part of TMS RADical Web via the route of the Mille Miglia.

Therefore, it is fitting that this TMS WEB Core v1.0 is named after Brescia, the start of the Mille Miglia and we will follow the track of the legendary year 1955. As you can see, the next milestone is Verona and so the next major release will be called Verona. In a follow-up blog, we will reveal some more details of the roadmap, the milestones of versions you can expect along this famous track. I would say, enjoy this exciting trip and follow with us along the track!

Bruno Fierens


Bookmarks: 

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




Previous  |  Next  |  Index