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.