Smart Mobile Studio client samples

In addition to Delphi and FreePascal clients, our framework is able to access any mORMot server from HTML5 / AJAX rich client, thanks to Smart Mobile Studio.

Adding two numbers in AJAX

You can find in SQLite3- CrossPlatform ClientsSmartMobileStudio a simple client for the TServiceCalculator.Add() interface-based service.
If your Project14ServerHttpWrapper server is running, you can just point to the supplied wwwhtml file in the sub-folder.
You would then see a web page with a "Server Connect" button, and if you click on it, you would be able to add two numbers. This a full HTML5 web application, connecting securely to your mORMot server, which will work from any desktop browser (on Windows, Mac OS X, or Linux), or from any mobile device (either iPhone / iPad / Android / Windows 8 Mobile).

In order to create the application, we just clicked on "download as file" in the SmartMobileStudio link in the web page, and copied the generated file in the source folder of a new Smart Mobile project.
Of course, we did copy the needed SynCrossPlatform*.pas units from the mORMot source code tree into the Smart library folder, as stated above. Just ensure you run CopySynCrossPlatformUnits.bat from the CrossPlatform folder at least once from the latest revision of the framework source code.

Then, on the form visual editor, we added a BtnConnect button, then a PanelCompute panel with two edit fields named EditA and EditB, and two other buttons, named BtnComputeAsynch and BtnComputeSynch. A LabelResult label will be used to display the computation result. The BtnConnect is a toggle which will show or display the PanelCompute panel, which is hidden by default, depending on the connection status.

In the Form1.pas unit source code side, we added a reference to our both SynCrossPlatformREST and mORMotClient units, and some events to the buttons:

unit Form1;
interface
uses
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms,
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Panel,
  SmartCL.Controls.Label, SmartCL.Controls.EditBox, SmartCL.Controls.Button,
  SynCrossPlatformREST, mORMotClient;

type TForm1 = class(TW3Form) procedure BtnComputeSynchClick(Sender: TObject); procedure BtnComputeAsynchClick(Sender: TObject); procedure BtnConnectClick(Sender: TObject); private {$I 'Form1:intf'} protected Client: TSQLRestClientURI; procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; end;

The BtnConnect event will connect asynchronously to the server, using 'User' as log-on name, and 'synopse' as password (those as the framework defaults).
We just use the GetClient() function, as published in our generated mORMotClient.pas unit:

/// create a TSQLRestClientHTTP instance and connect to the server
// - it will use by default port 888
// - secure connection will be established via TSQLRestServerAuthenticationDefault
// with the supplied credentials
// - request will be asynchronous, and trigger onSuccess or onError event
procedure GetClient(const aServerAddress, aUserName,aPassword: string;
  onSuccess, onError: TSQLRestEvent; aServerPort: integer=SERVER_PORT);

It uses two callbacks, the first in case of success, and the second triggered on failure. On success, we will set the global Client variable with the TSQLRestClientURI instance just created, then display the two fields and compute buttons:

procedure TForm1.BtnConnectClick(Sender: TObject);
begin
  if Client=nil then
    GetClient('127.0.0.1','User','synopse',
      lambda (aClient: TSQLRestClientURI)
        PanelCompute.Visible := true;
        W3Label1.Visible := true;
        W3Label2.Visible := true;
        LabelConnect.Caption := '';
        BtnConnect.Caption := 'Disconnect';
        LabelResult.Caption := '';
        Client := aClient;
      end,
      lambda
        ShowMessage('Impossible to connect to the server!');
      end)
  else begin
    PanelCompute.Visible := false;
    BtnConnect.Caption := 'Server Connect';
    Client.Free;
    Client := nil;
  end;
end;

The GetClient() function expects two callbacks, respectively onSuccess and onError, which are implemented here with two SmartPascal lambda blocks.

Now that we are connected to the server, let's do some useful computation!
As you can see in the mORMotClient.pas generated unit, our interface-based service can be accessed via a SmartPascal TServiceCalculator class (and not an interface), with two variations of each methods: one asynchronous method - e.g. TServiceCalculator.Add() - expecting success/error callbacks, and one synchronous (blocking) method - e.g. TServiceCalculator._Add():

type
  /// service accessible via http://localhost:888/root/Calculator
  // - this service will run in sicShared mode
  // - synchronous and asynchronous methods are available, depending on use case
  // - synchronous _*() methods will block the browser execution, so won't be
  // appropriate for long process - on error, they may raise EServiceException
  TServiceCalculator = class(TServiceClientAbstract)
  public
    /// will initialize an access to the remote service
    constructor Create(aClient: TSQLRestClientURI); override;
    procedure Add(n1: integer; n2: integer;
      onSuccess: procedure(Result: integer); onError: TSQLRestEvent);
    function _Add(const n1: integer; const n2: integer): integer;
  end;

We can therefore execute asynchronously the Add() service as such:

procedure TForm1.BtnComputeAsynchClick(Sender: TObject);
begin
  TServiceCalculator.Create(Client).Add(
    StrToInt(EditA.Text),StrToInt(EditB.Text),
    lambda (res: integer)
      LabelResult.Caption := format('Result = %d',[res]);
    end,
    lambda
      ShowMessage('Error calling the method!');
    end);
end;

Or execute synchronously the _Add() service:

procedure TForm1.BtnComputeSynchClick(Sender: TObject);
begin
  LabelResult.Caption := format('Result = %d',
    [TServiceCalculator.Create(Client)._Add(
      StrToInt(EditA.Text),StrToInt(EditB.Text))]);
end;

Of course, the synchronous code is much easier to follow and maintain. To be fair, the SmartPascal lambda syntax is not difficult to read nor write. In the browser debugger, you can easily set a break point within any lambda block, and debug your code.

Note that if the server is slow to answer, your whole web application will be unresponsive, and the browser may even complain about the page, proposing the kill its process!
As a consequence, simple services may be written in a synchronous manner, but your serious business code should rather use asynchronous callbacks, just as with any modern AJAX application.

Thanks to the Smart Linking feature of its compiler, only the used version of the unit will be converted to JavaScript and included in the final index.html HTML5 file. So having both synchronous and asynchronous versions of each method at hand is not an issue.

CRUD/ORM remote access

If the server did have some ORM model, its TSQLRecord classes will also be part of the mORMotClient.pas generated unit. All types, even complex record structures, will be marshaled as expected.

For instance, if you run the RegressionTestsServer.dpr server (available in the same folder), a much more complete unit could be generated from http://localhost:888/root/wrapper:

type // define some enumeration types, used below
  TPeopleSexe = (sFemale, sMale);
  TRecordEnum = (reOne, reTwo, reLast);

type // define some record types, used as properties below TTestCustomJSONArraySimpleArray = record F: string; G: array of string; H: record H1: integer; H2: string; H3: record H3a: boolean; H3b: TSQLRawBlob; end; end; I: TDateTime; J: array of record J1: byte; J2: TGUID; J3: TRecordEnum; end; end; type /// service accessible via http://localhost:888/root/Calculator // - this service will run in sicShared mode // - synchronous and asynchronous methods are available, depending on use case // - synchronous _*() methods will block the browser execution, so won't be // appropriate for long process - on error, they may raise EServiceException TServiceCalculator = class(TServiceClientAbstract) public /// will initialize an access to the remote service constructor Create(aClient: TSQLRestClientURI); override; procedure Add(n1: integer; n2: integer; onSuccess: procedure(Result: integer); onError: TSQLRestEvent); function _Add(const n1: integer; const n2: integer): integer; procedure ToText(Value: currency; Curr: string; Sexe: TPeopleSexe; Name: string; onSuccess: procedure(Sexe: TPeopleSexe; Name: string); onError: TSQLRestEvent); procedure _ToText(const Value: currency; const Curr: RawUTF8; var Sexe: TPeopleSexe; var Name: RawUTF8); procedure RecordToText(Rec: TTestCustomJSONArraySimpleArray; onSuccess: procedure(Rec: TTestCustomJSONArraySimpleArray; Result: string); onError: TSQLRestEvent); function _RecordToText(var Rec: TTestCustomJSONArraySimpleArray): string; end;
/// map "People" table TSQLRecordPeople = class(TSQLRecord) protected fFirstName: string; fLastName: string; fData: TSQLRawBlob; fYearOfBirth: integer; fYearOfDeath: word; fSexe: TPeopleSexe; fSimple: TTestCustomJSONArraySimpleArray; // those overriden methods will emulate the needed RTTI class function ComputeRTTI: TRTTIPropInfos; override; procedure SetProperty(FieldIndex: integer; const Value: variant); override; function GetProperty(FieldIndex: integer): variant; override; public property FirstName: string read fFirstName write fFirstName; property LastName: string read fLastName write fLastName; property Data: TSQLRawBlob read fData write fData; property YearOfBirth: integer read fYearOfBirth write fYearOfBirth; property YearOfDeath: word read fYearOfDeath write fYearOfDeath; property Sexe: TPeopleSexe read fSexe write fSexe; property Simple: TTestCustomJSONArraySimpleArray read fSimple write fSimple; end;

In the above code, you can see several methods to the ICalculator service, some involving the complex TTestCustomJSONArraySimpleArray record type. The implementation section of the unit will in fact allow serialization of such records to/from JSON, even with obfuscated JavaScript field names, via ComputeRTTI() GetProperty() and SetProperty().

Some enumerations types are also defined, so will help your business code be very expressive, thanks to the SmartPascal strong typing. This is a huge improvement when compared to JavaScript native weak and dynamic typing.

There is a TSQLRecordPeople class generated, which will map the following Delphi class type, as defined in the PeopleServer.pas unit:

  TSQLRecordPeople = class(TSQLRecord)
  protected
    fData: TSQLRawBlob;
    fFirstName: RawUTF8;
    fLastName: RawUTF8;
    fYearOfBirth: integer;
    fYearOfDeath: word;
    fSexe: TPeopleSexe;
    fSimple: TTestCustomJSONArraySimpleArray;
  public
    class procedure InternalRegisterCustomProperties(Props: TSQLRecordProperties); override;
  published
    property FirstName: RawUTF8 read fFirstName write fFirstName;
    property LastName: RawUTF8 read fLastName write fLastName;
    property Data: TSQLRawBlob read fData write fData;
    property YearOfBirth: integer read fYearOfBirth write fYearOfBirth;
    property YearOfDeath: word read fYearOfDeath write fYearOfDeath;
    property Sexe: TPeopleSexe read fSexe write fSexe;
  public
    property Simple: TTestCustomJSONArraySimpleArray read fSimple;
  end;

Here, a complex TTestCustomJSONArraySimpleArray record field has been published, thanks to a manual InternalRegisterCustomProperties() registration, as we already stated above.
You can see that types like RawUTF8 were mapped to the standard SmartPascal string type, as expected, when converted to the mORMotClient.pas generated unit.

Your AJAX client can then access to this TSQLRecordPeople content easily, via standard CRUD operations.
See the SQLite3- SmartMobileStudio Client sample, for instance the following line:

  people := new TSQLRecordPeople;
  for i := 1 to 200 do begin
    assert(client.Retrieve(i,people));
    assert(people.ID=i);
    assert(people.FirstName='First'+IntToStr(i));
    assert(people.LastName='Last'+IntToStr(i));
    assert(people.YearOfBirth=id+1800);
    assert(people.YearOfDeath=id+1825);
  end;

Here, the client variable is a TSQLRestClientURI instance, as returned by the GetClient() onSuccess callback generated in mORMotClient.pas.
You have Add() Delete() Update() FillPrepare() CreateAndFillPrepare() and Batch*() methods available, ready to safely access your data from your AJAX client.

If you update your data model on the server, just re-generate your mORMotClient.pas unit from http://localhost:888/root/wrapper, then rebuild your Smart Mobile Studio project to reflect all changes made to your ORM data model, or your SOA available services.

Thanks to the SmartPascal strong typing, any breaking change of the server expectations would immediately be reported at compilation, and not at runtime, as it would with regular JavaScript clients.