After reading various blog posts about OP4JS (now named “Smart Mobile Studio”), I was very curious if I could use it for my current customer. We have a couple of RemObjects services, which contain our business logic (multi tier, SOA (service oriented architecture)), and we would like to create a HTML client for it (for iPad and Android).
I did a simple test with the latest alpha release, and it turned out to be relative easy! (it is an alpha, so no code completion, internal debugging, delphi shortcuts etc yet, so I had to do everything by hand :-) ).
First I made a simple RemObjects server using the Delphi Wizard:
Delphi -> New -> Other -> RemObjects SDK -> VCL Standalone:
I selected HTTP server + JSON message format (needed for Javascript communication).
Default it already contains 2 functions: “Sum(A,B: Integer): Integer” and “GetServerTime: TDatetime” (see below for screenshot of the RemObjects Service Builder). When compiling the server for first time, it will generate a datamodule for the server side implementation for these 2 functions (I won’t post the implemention: a “sum” function is too simple :-) ). So we have a RemObjects HTTP server now, which listens to the default port 8099 and uses the JSON data format (normally we use TCP + Binary messages for fast and low overhead messaging).
Next, we need to generate some code for the client side. In the RemObjects Service Builder we can generate various client implementations: C++, PHP, Delphi (also automatic done in Delphi IDE) and also Javascript. In the latest RemObjects release they added a completely new Javascript implementation but I could not get it working yet, so I have taken the "old" JSON RPC implementation:
You need to download some extra Javascript files, because it is dependent on the Yahoo YUI toolkit (I have tested the RemObjects Javascript implementation before, so I already had these files).
After that, I started “Smart Mobile Studio” and played with the delivered demos. I created a new “visual project”, and added 2 edits, a button, and another edit to the form (all typing by hand, no designer yet):
I did a simple test with the latest alpha release, and it turned out to be relative easy! (it is an alpha, so no code completion, internal debugging, delphi shortcuts etc yet, so I had to do everything by hand :-) ).
First I made a simple RemObjects server using the Delphi Wizard:
Delphi -> New -> Other -> RemObjects SDK -> VCL Standalone:
I selected HTTP server + JSON message format (needed for Javascript communication).
Default it already contains 2 functions: “Sum(A,B: Integer): Integer” and “GetServerTime: TDatetime” (see below for screenshot of the RemObjects Service Builder). When compiling the server for first time, it will generate a datamodule for the server side implementation for these 2 functions (I won’t post the implemention: a “sum” function is too simple :-) ). So we have a RemObjects HTTP server now, which listens to the default port 8099 and uses the JSON data format (normally we use TCP + Binary messages for fast and low overhead messaging).
Next, we need to generate some code for the client side. In the RemObjects Service Builder we can generate various client implementations: C++, PHP, Delphi (also automatic done in Delphi IDE) and also Javascript. In the latest RemObjects release they added a completely new Javascript implementation but I could not get it working yet, so I have taken the "old" JSON RPC implementation:
You need to download some extra Javascript files, because it is dependent on the Yahoo YUI toolkit (I have tested the RemObjects Javascript implementation before, so I already had these files).
After that, I started “Smart Mobile Studio” and played with the delivered demos. I created a new “visual project”, and added 2 edits, a button, and another edit to the form (all typing by hand, no designer yet):
Procedure TForm1.InitializeObject;
Begin
inherited;
FEdit1:=TW3EditBox.Create(self);
FEdit1.setBounds(10,10,100,32);
FEdit1.text:='2';
FEdit2:=TW3EditBox.Create(self);
FEdit2.setBounds(10,50,100,32);
FEdit2.text:='3';
FBackBtn:=TW3Button.Create(self);
FBackBtn.setBounds(10,95,100,32);
FBackBtn.Caption:='Sum';
FBackBtn.Visible:=true;
FBackBtn.OnClick := ButtonClicked;
FEdit3:=TW3EditBox.Create(self);
FEdit3.setBounds(10,140,100,32);
FEdit3.text:='';
End;
I created a “button click” procedure, which calls my RemObjects server. Because I have to call some pure Javascript code, I used the “asm” keyword for my “low level” code:
Procedure TForm1.ButtonClicked(Sender:TObject);
var
s1, s2, s3: string;
Begin
s1 := Self.FEdit1.text;
s2 := Self.FEdit2.text;
asm
var service = new NewService("http://localhost:8099/JSON");
service.Sum(@s1,
@s2,
function(sumresult)
{
TW3EditBox.setText(Self.FEdit3,sumresult);
});
end;
End;
Note: I had to put the values of the 2 edits in seperate variables, because I could not directly use “@FEdit1.text” in the “asm” block? For the result callback (async!) I had to do something different, so I used “TW3EditBox.setText(FEdit3,result)” because this code is generated by SmartMobileStudio too when you type “FEdit3.text := ‘value’.
This code almost works: I have to include the client Javascript files somewhere. You can include Javascript in the project, but also in the main index.html (like you normally do with html).
This code almost works: I have to include the client Javascript files somewhere. You can include Javascript in the project, but also in the main index.html (like you normally do with html).
However, SmartMobileStudio uses a temp dir when running from the IDE (so it misses my extra .js files), so I just compiled the project, and ran the generated index.html directly from the project dir in Google Chrome.script type="text/javascript" src="lib/yahoo.js">script type="text/javascript" src="lib/connection.js">
script type="text/javascript" src="lib/event.js">
script type="text/javascript" src="lib/json.js">
So it’s done now? No, I got an error in Chrome, because I call an other HTTP server from my (local) index.html:
I had to enable cross site calls in myRemObjects HTTP server:
ROServer.SendCrossOriginHeader := True;But then Chrome complains about “Access-Control-Allow-Headers”, so I manually changed this by overriding the RemObjects implementation of the internal Indy “Other” function:
ROHttpServerCommandOther := ROServer.IndyServer.OnCommandOther; ROServer.IndyServer.OnCommandOther := InternalServerCommandOther;
procedure TServerForm.InternalServerCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
//original RO handling
ROHttpServerCommandOther(AContext, ARequestInfo, AResponseInfo);
//need to set extra allow-header
if ROServer.SendCrossOriginHeader then
AResponseInfo.CustomHeaders.Values['Access-Control-Allow-Headers'] := 'Origin, X-Requested-With, Content-Type';
end;
And then it works! After the initial problems, it should be easy now to extend the functionality further. For example, I would like to create a "Javascript to Delphi wrapper class" generator based on the .rodl interface file, so I have type safety again (no asm sections in code, only in wrapper class).
I hope to blog about this more in the future...
Note: demo code + binaries can be found here. First start "\ROSmartTest\NewProject.exe" and then "\Project1\Project1\index.html"
1 opmerking:
To get technical:
“@FEdit1.text” in the “asm” block?
Currently you would need "@Self.FEdit1" in the asm, but "text" is a property, so not safe in asm (since it could change between a field access or a getter, which could be renamed by the obfuscator, etc.).
However you could use variants to minimize the asm, with something like (out of the top of my head):
var service, setResultFunc : Variant;
...
asm
@service = new NewService("http://localhost:8099/JSON");
@setResultFunc = function(sumresult) {
@TW3EditBox.setText(@Self.FEdit3,sumresult);
};
end;
service.Sum(Self.FEdit1.Text, Self.FEdit2.Text, setResultFunc);
This way the property reads are handled by the compiler rather than the asm, and the asm should survive obfuscation unscathed.
Een reactie posten