Archive

Archive for November, 2015

Smart wrappers, twirk like jQuery

November 4, 2015 3 comments

I was asked how to better implement a “JQuery like syntax” – but for Smart Pascal rather than surrendering to alien and cryptic JavaScript. Well, there are many ways to skin a cat, but below is a typical example of how can wrap interesting functionality more or less like jQuery does it; Except within the confines of Smart Pascal.

But naturally, object pascal is a much more rigid language. It has rules and clear principles which cant (and should not) be abused. Also, class helpers is the proper way to extend the functionality of classes through abstraction.

Anyways, here is a way you could do it:

  TEventReference = Class(TObject)
  private
    FInstance:  TW3TagObj;
    FEventName: String;
  public
    procedure   &To(EntryPoint:TProcedureRef);
    constructor Create(Obj:TW3TagObj;EventName:String);
  end;

  TControlReference = class(TObject)
  private
    FParent:    TW3Component;
    FInstance:  TW3TagObj;
  public
    function    Attach(EventName:String):TEventReference;
    constructor Create(AParent:TW3Component;AInstance:TW3TagObj);
  end;

  TSelector = class abstract
  public
    class function &Object(Container:TW3Component;ControlName:String):TControlReference;
  end;

//#############################################################################
// TEventReference
//#############################################################################

constructor TEventReference.Create(Obj:TW3TagObj;EventName:String);
begin
  inherited Create;
  FInstance := Obj;
  FEventName := EventName;
end;

procedure TEventReference.&To(EntryPoint:TProcedureRef);
begin
  w3_AddEvent(FInstance.Handle,FEventName,Entrypoint);
end;

//#############################################################################
// TControlReference
//#############################################################################

constructor TControlReference.Create(AParent:TW3Component;AInstance:TW3TagObj);
begin
  inherited Create;
  FParent := AParent;
  FInstance := AInstance;
end;

function TControlReference.Attach(EventName:String):TEventReference;
begin
  result := TEventReference.Create(Finstance,EventName);
end;

//#############################################################################
// TSelector
//#############################################################################

class function TSelector.&Object(Container:TW3Component;ControlName:String):TControlReference;
var
  x: Integer;
  mObj: TObject;
begin
  result := NIL;
  if Container<>NIL then
  begin
    controlname:=controlname.trim();
    if controlname.length>0 then
    begin
      mObj := Container.ChildByName(ControlName);
      if mObj<>NIL then
      result := TControlReference.Create(Container,TW3Component(mObj));
    end;
  end;
end;

What is this good for?

The above code does very little. It is just an example of how you can build chains of functionality using standard object-orientation. In this case all we do is to to:

  • Locate a component by name from a container
  • Bind to an event by name
  • Attach event handler to event

But it may be helpful for others to see how to do this, so I figured it was worth 5 minutes.

The syntax goes like this:

TSelector.Object(Form1,'w3button1').Attach('onclick').To( procedure ()
  begin
    // your event handler here
  end);

Note: Since JavaScript has automatic garbage collection you don’t need to worry about creating instances on the fly, they are collected and dropped by the VM as soon as they go out of scope.

To simplify things even more, we can declare TSelector as a global (unit) variable:

var
  JQuery: TSelector;

And now we can do the same thing, but using the variable name instead:

JQuery.object(Form1,'w3button1').Attach('onclick').To( procedure ()
  begin
    // your event handler here
  end);

More advanced stuff

if you want to make use of property and array references, which would be more natural to mimic jQuery and other classic JS libraries – you will have to create an instance and serve that through a function.

var
__JQUERY: TSelector;

function JQuery:TSelector;
begin
  result := __JQUERY;
end;

initialization
begin
  __JQUERY := TSelector.Create;
end;

With this in place we can add a default property, as such:

  TSelector = class(TObject)
  private
    function getForm(name:String):TW3CustomForm;
  public
    property Forms[name:String]:TW3CustomForm read getForm;default;
    class function &Object(Container:TW3Component;ControlName:String):TControlReference;
  end;

And now we can can support a more direct approach (must also alter the sub classes accordingly):

JQuery['form1'].with('w3button1').attach('onclick').to( procedure ()
  begin
    // your event handler here
  end);

Smart platform services

November 4, 2015 2 comments

Something cool is taking place in the Smart Mobile Studio RTL, namely the implementation of platform services.

What is a platform service you say? Put simply a platform service is an API that the RTL exposes for you while abstracting you from the low-level implementation. Where the existing browser-driver system is platform spesific, dealing with how the browser exposes it’s features (which is different from browser to browser), platform services are feature spesific: meaning that they present a set of features un-bound by the environment.

This means that even though browsers implement things differently (even with different names, attributes and method calls), you dont have to worry about it.

Platform services are available (read: resolved) at runtime rather than design-time. So you dont have to pick out the services from a list prior to compilation, instead the RTL will create the correct object factory – which in turn exposes the correct service-item for the browser you use. The number of services available depends on what you have used, which the compiler takes care of.

What features?

Services deal with features that the browsers may or may-not expose, hence the service architecture for querying what you can and cannot do. For instance, access to the camera or image-picker is a typical service API, because it greatly depends on the mobile-device, what JS API’s are available (phonegap or native access) – and last but not least: user interaction.

Other types of services are more low-level in nature: websocket support, rest-client support, JSONP support, data encoding mechanisms, encryption support, access to gps tracking, camera access, disk access, storage functionality, database access.

if PlatformServices.GetCameraAPI(IAccess) then
begin
  if IAccess.ShowCamera(mediastream, [ocTakePhoto,ocPhotoPicker]) then
  begin
    //mediastream now contains picture or movie frames
  end else
  writeln('debug: camera was canceled');
end else
raise exception.create('Camera not supported error');

Now in order for us to shield you from the gory details we have started to implement the most common API’s as run-time services. This means easy to use, easier for us to maintain and setting a standard for future expansion of the Smart Mobile Studio run-time library.

Working with platform services

Coding based on platform services is all about abstraction. You simply ask the system to provide you with access to a service, and if the system supports that service (or allows it) you are given an access interface. The abstraction layer and working with interfaces like this is very effective. You can of course create class instances ad-hoc if you like, but that kinda defeats the portability of your code and binds you to that spesific class (which may be webkit spesific, firefox spesific or something else). The goal of platform services is to make sure your code runs and behaves identical on all supported platforms.

Websocket example

Here is an example of how to access the websocket platform service, using the access interface to create a websocket element, initialize it and then connect to a server:

procedure TForm1.btnConnectClick(Sender: TObject);
var
  IAccess:  IW3WebSocketService;
  ISocket:  IWebSocket;
begin
  if PlatformServices.GetWebSocketAPI(IAccess) then
  begin
    if IAccess.CreateSocket(ISocket) then
    Begin

      //Attach connected event handler
      ISocket.SetOnOpenEvent( procedure (Access:IWebSocket)
        begin
          Writeln('Websocket is now open!');
        end);

      // Attach disconnect event handler
      ISocket.SetOnClosedEvent( procedure (Access:IWebSocket)
        begin
          Writeln('Websocket is now closed');
        end);

      // Attach Message handler
      ISocket.SetOnMessageEvent( procedure (IO:IWebSocketIO;
                                 Message:TWebSocketMessageData)
        begin
          WriteLn(message.mdText);
        end);

      // Connect to our server
      ISocket.Connect('ws://192.168.38.108:8080',['void']);
    end;
  end;
end;

As you can see from the code above you are pretty much shielded from all the technical aspects of the WebSocket standard. You simply ask for the service and then use the service to create a websocket instance – which you access through the socket interface.

As a user you don’t have to care about the gory details here. You don’t have to worry about stuff like instance factories, if your code is running on FireFox, Chrome, MS10 or Safari. And even better is that you no longer have to care if it’s running on desktop, iOS, Android or an embedded system.

Here is the full IWebSocket interface

  IWebSocketIO = interface
    Procedure WriteString(const value:String);
    procedure WriteMemory(const Value:TMemoryHandle);
    Procedure WriteStream(const Value:TStream);
    Procedure WriteBinary(Const Data:TBinaryData);

    function  GetSocketState:TWebSocketState;
    function  GetConnected:Boolean;
    function  GetURL:String;
    function  GetProtocol:String;

    function  GetBinaryMessageDataType:TWebSocketBinaryDataType;
    procedure SetBinaryMessageDataType(const Value:TWebSocketBinaryDataType);

    procedure Disconnect;

    Property  BinaryMessageDataType:TWebSocketBinaryDataType
              read GetBinaryMessageDataType
              write SetBinaryMessageDataType;

    Property  SocketState:TWebSocketState read getSocketState;
    Property  Connected:Boolean read getConnected;
    property  URL:String read getURL;
    Property  Protocol:String read getProtocol;
  end;

  (* Websocket service interface *)
  IWebSocket = interface
    function  GetBinaryMessageDataType:TWebSocketBinaryDataType;
    procedure SetBinaryMessageDataType(const Value:TWebSocketBinaryDataType);

    function  GetSocketState:TWebSocketState;
    function  GetConnected:Boolean;
    function  GetURL:String;
    function  GetProtocol:String;

    procedure Connect(URL:String;Protocols:Array of String);
    procedure Disconnect;

    Procedure WriteString(const value:String);
    procedure WriteMemory(const Value:TMemoryHandle);
    Procedure WriteStream(const Value:TStream);
    Procedure WriteBinary(Const Data:TBinaryData);

    function  GetOnOpenEvent:TWebSocketOpenEvent;
    function  GetOnClosedEvent:TWebSocketCloseEvent;
    function  GetOnMessageEvent:TWebSocketMessageEvent;
    function  GetOnErrorEvent:TWebSocketErrorEvent;

    procedure SetOnOpenEvent(const Handler:TWebSocketOpenEvent);
    procedure SetOnClosedEvent(const Handler:TWebSocketCloseEvent);
    procedure SetOnMessageEvent(const Handler:TWebSocketMessageEvent);
    procedure SetOnErrorEvent(const Handler:TWebSocketErrorEvent);

    Property  SocketState:TWebSocketState read getSocketState;
    Property  Connected:Boolean read getConnected;
    property  URL:String read getURL;
    Property  Protocol:String read getProtocol;

    Function  GetServiceElement:IServiceElement;

    Property  BinaryMessageDataType:TWebSocketBinaryDataType
              read GetBinaryMessageDataType
              write SetBinaryMessageDataType;
    Property  OnOpen:TWebSocketOpenEvent read GetOnOpenEvent write SetOnOpenEvent;
    Property  OnClosed:TWebSocketCloseEvent read GetOnClosedEvent write SetOnClosedEvent;
    Property  OnMessage:TWebSocketMessageEvent read GetOnMessageEvent write SetOnMessageEvent;
    Property  OnError:TWebSocketErrorEvent read GetOnErrorEvent write SetOnErrorEvent;
  end;

Keep it simple, keep it safe

The benefit for us in terms of delivering a better Smart Mobile Studio, is that it allows us to compartmentalize our code. We get to create service classes for FireFox, Chrome and so on without having to go through hoops just to make it work identically everywhere. It works almost like a driver or plugin system, where the correct factory and service provider (sigh) is resolved at runtime. Most of the time we get away with a single implementation class, but it’s nice to know that we can isolate code for one browser in its own class, and support for the same features for another browser in another class. This helps keep the RTL sane and organized.

Platform services is pretty cool and saves you a lot of time

Platform services is pretty cool and saves you a lot of time

While no date has been set, I can at least tell you that this system will not be available until we reach at least version 2.3.x; at the very least.