Archive
Sneak peek at the new Smart RTL
There are many new and powerful features in the upcoming release of Smart Mobile Studio. And one of the smallest, yet most powerful and useful features – is the ability to be notified when a value or property of an object is altered.
So far, TVariant has just been a helper class for dealing with typical tasks. Well, perhaps not just typical because under Smart Pascal, variants can map directly to a javascript object (of any type) and allows you direct access to its prototype.
In the next update TVariant has a few new members, most notably: watch() and unwatch().
As their name imply they allow you to be notified whenever a single property, or indeed – a property representing a child object, is altered. It may sound trivial but it’s actually one of those features that lays the foundation for responsive, data-aware controls. But you can use it for many other things as well, like keeping tab’s on async calls that alters some value. Wouldnt it be cool to be notified when a picture has finished loading? Well, TW3Image has events for that, but being able to achieve the same results with different means is cool. Well, now you can!
Here is how you can use it:
// create an empty javascript object var MyValue: Variant; MyValue := TVariant.CreateObject(); MyValue.NewProperty := 12; // add a property and set a value // We want to know when the object changes TVariant.Watch(MyValue, "NewProperty", procedure () begin showmessage("You changed the value!"); end); // Wait 2 seconds and then change the value TW3Dispatch.Execute( procedure () begin MyValue.NewProperty := 24; end, 2000);
The above code produces, as expected, a message dialog with the text “You changed the value!” 2 seconds after. So the moment “MyValue.NewProperty := 24” is called, the underlying mechanism picks that up and informs you of it through an anonymous call. Removing a watch is equally simple:
// Remove the watchdog callback from the javascript prototype TVariant.UnWatch(MyValue, "Newproperty");
As of writing only Firefox supports this natively: but dont worry! We use the now universal and standard polyfill written by Eli Grey. So this polyfill is compiled into your application regardless. Its essentially now a standard part of the RTL. We have tested it on iOS, Android, Chrome, Internet Explorer, Spartan and even node.js and it performs brilliantly with little or no penalty with regards to speed. The polyfill simply injects itself as a new getter/setter and keeps track of the old, that’s how it can intercept values without causing problems with the intrinsic mechanisms in the JSVM. For those interested, here is the polyfill:
/* * object.watch polyfill * * 2012-04-03 * * By Eli Grey, http://eligrey.com * Public Domain. * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. */ // object.watch if (!Object.prototype.watch) { Object.defineProperty(Object.prototype, "watch", { enumerable: false , configurable: true , writable: false , value: function (prop, handler) { var oldval = this[prop] , newval = oldval , getter = function () { return newval; } , setter = function (val) { oldval = newval; return newval = handler.call(this, prop, oldval, val); } ; if (delete this[prop]) { // can't watch constants Object.defineProperty(this, prop, { get: getter , set: setter , enumerable: true , configurable: true }); } } }); } // object.unwatch if (!Object.prototype.unwatch) { Object.defineProperty(Object.prototype, "unwatch", { enumerable: false , configurable: true , writable: false , value: function (prop) { var val = this[prop]; delete this[prop]; // remove accessors this[prop] = val; } }); }
TW3Dispatch
Did you notice that I used TW3Dispatch.Execute() rather than the old w3_callback() function? Indeed. Part of the cleanup we are doing is not just about new and fancy code. Its also about removing duplicate code (which we had a lot of) and to organize as much as possible into classes or namespaces.
TW3Dispatch is a class that deals with delayed execution of code. It is stored in the new System.Time.pas unit and is marked as a partial class. Here is what the basis looks like under System.Time.pas:
TW3DispatchHandle = THandle; (* This class is a thin wrapper around the essential JavaScript callback and time-scheduling methods (for delayed or repeated execution of code). It is isolated here to protect the user against changes in future revisions of javascript, and also different environments that has not yet reached the market. This gives us space to adapt to changes without affecting your codebase *) TW3Dispatch = partial class class function Execute(const EntryPoint:TProcedureRef; const WaitForInMs: integer): TW3DispatchHandle;overload; class procedure CancelExecute(const Handle: TW3DispatchHandle); class procedure RepeatExecute(const Entrypoint: TProcedureRef; const RepeatCount: integer; const IntervalInMs: integer); class function SetTimeOut(const Entrypoint: TProcedureRef; const WaitForInMs: integer): TW3DispatchHandle; class procedure ClearTimeOut(const Handle: TW3DispatchHandle); class function SetInterval(const Entrypoint: TProcedureRef; const IntervalDelayInMS: integer): TW3DispatchHandle; class procedure ClearInterval(const Handle: TW3DispatchHandle); class function JsNow: JDate; class function Ticks: integer; class function TicksOf(const Present: TDateTime): integer; class function TicksBetween(const Past, Future: TDateTime): integer; end;
The reason the class is marked as partial, is because depending on the framework you are using (SmartNJ for node.js, or SmartCL for visual applications), TW3Dispatch is expanded accordingly. If we move over to the SmartCL RTL folder and look at the file SmartCL.System.pas (yes, the same units exist in the different namespaces), TW3Dispatch gains the following, DOM related functionality:
(* TW3Dispatch is defined in the System.Time unit. We complete the partial class here with DOM spesific functionality *) TW3Dispatch = partial class public class function Ready:Boolean; class procedure ExecuteDocumentReady(const OnReady:TProcedureRef); class function RequestAnimationFrame(const Entrypoint: TProcedureRef): TW3DispatchHandle;virtual; class procedure CancelRequestAnimationFrame(const Reference: TW3DispatchHandle);virtual; end;
The Ready() function is a handy one, it tells you if the browser has finished loading associated scripts and resources and that the document object model is, well, ready to be used. But while that is handy (especially in Form.Initialization() and similar spots that can be called while the DOM is not yet finished), ExecuteDocumentReady() makes it almost fun to write controls and get the startup of your application synchronized.
As you probably know JavaScript is ASYNC by nature. This means that any attempt to force it to behave like traditional object pascal is a gamble at best. Earlier you had to make due with a timer callback to give you some delay before performing a graphics, but now you can use ExecuteDocumentReady() to make sure everything is in order before you execute that special piece of code. It takes a single anonymous procedure, like this:
TW3Dispatch.ExecuteDocumentReady( procedure () begin showmessage("Its now safe to modify the DOM!"); end);
Now the amount of changes and alterations to the RTL is in the hundreds, so explaining them all here is beyond the scope of this post. But here are a few more classes that makes your life easier:
TW3MouseCursor = static class public class function CursorByName(const CursorName: string): TCursor; class function NameByCursor(const Cursor: TCursor): String; class function GetCursorFromElement(const Handle: TControlHandle): TCursor; class procedure SetCursorForElement (const Handle: TControlHandle; const Cursor: TCursor); end; TW3DOMDeviceCapabilities = class(TW3CustomDeviceCapabilities) protected function GetMouseSupport: boolean;override; function GetTouchSupport: boolean;override; function GetGamePadSupport: boolean;override; function GetKeyboardSupported: boolean;override; function GetDevicePixelRatio: float;virtual; function GetDisplayPixelsPerInch: TPixelsPerInch;virtual; public property DevicePixelRatio: float read GetDevicePixelRatio; property DisplayPixelsPerInch: TPixelsPerInch read GetDisplayPixelsPerInch; end; TW3TweenEngine = class private FTimer: TW3Timer; FValues: Array of TW3TweenElement; FActive: boolean; FPartial: boolean; FInterval: integer; FLookup: TW3ObjDictionary; procedure SetInterval(const Value: integer); protected procedure HandleSyncUpdate;virtual; procedure HandleUpdateTimer(Sender: TObject);virtual; function Update(const Item: TW3TweenElement):double;virtual; protected property Dictionary:TW3ObjDictionary read FLookup; procedure TweenStarted(const Item:TW3TweenElement);virtual; procedure TweenComplete(const Item:TW3TweenElement);virtual; procedure TweenPaused(const Item:TW3TweenElement);virtual; procedure TweenResumed(const Item:TW3TweenElement);virtual; public function ObjectOf(const Id: string):TW3TweenElement; function IndexOf(Id: string): integer; property Active: boolean read ( FActive ); property Item[const index: integer]:TW3TweenElement read (FValues[index]); property Tween[const Id: string]:TW3TweenElement read ObjectOf;default; property Count:Integer read ( FValues.Length ); property Interval: integer read FInterval write SetInterval; property SyncRefresh: boolean; property IgnoreOscillate: boolean; function Add(Id: string): TW3TweenElement;overload; function Add(Id: string; const StartValue, Distance,Duration: double; const EaseObject: TW3TweenEase; const Behavior: TW3TweenBehavior): TW3TweenElement;overload; function Add(const Instance: TW3TweenElement): TW3TweenElement;overload; procedure Delete(index: integer);overload; procedure Delete(Id: string);overload; procedure Delete(const IdList: TStrArray);overload; procedure Clear;overload; procedure Execute;overload; procedure Execute(const Finished: TProcedureRef);overload; procedure Execute(const TweenObjects: Array of TW3TweenElement);overload; procedure Pause(const Index: integer);overload; procedure Pause(const Tween: TW3TweenElement);overload; procedure Pause(const Objs: array of TW3TweenElement);overload; procedure Pause(const Ids: array of string);overload; procedure Pause;overload; procedure Resume(const index: integer);overload; procedure Resume(const Tween: TW3TweenElement);overload; procedure Resume(const Objs: array of TW3TweenElement);overload; procedure Resume(const Ids: array of String);overload; procedure Resume;overload; procedure Cancel;overload; class function TimeCode: double; constructor Create;virtual; destructor Destroy;Override; published property OnPartial: TW3TweenPartialEvent; property OnUpdated: TW3TweenUpdatedEvent; property OnFinished: TW3TweenFinishedEvent; property OnStarted: TW3TweenStartedEvent; end; TMutationObserver = Class(TObject) public FObserving: Boolean; FHandle: THandle; FTarget: THandle; protected procedure CBMutationChange(mutationRecordsList:variant);virtual; public Property OnDisconnect:TNotifyEvent; Property OnConnect:TNotifyEvent; property OnChanged:TMutationObserverEvent; Property Handle:THandle read FHandle; Property TargetHandle:THandle read FTarget; Property Options:TMutationObserverOptions; Property Observing:Boolean read FObserving; Procedure Observe(targetHandle:THandle); Procedure Disconnect; Constructor Create;virtual; Destructor Destroy;Override; end; TW3StyleSheet = Class(TObject) private FHandle: THandle; protected function GetSheet: THandle; function GetRules: THandle; function GetCount: integer; function GetItem(const Index: integer): string; public Property Sheet: THandle read GetSheet; Property Handle: THandle read FHandle; function Add(RuleName: string; const aRules: string): string;overload; Procedure Add(const aRuleText: string);overload; Property Count:Integer read GetCount; Property Items[const Index: integer]: string read GetItem; class procedure AddClassToElement(const aElement:THandle;const aName:String); class procedure RemoveClassFromElement(const aElement:THandle;const aName:String); class function FindClassInElement(const aElement:THandle;const aName:String):Boolean; Constructor Create;virtual; Destructor Destroy;Override; End; TW3CustomAsset = class(TW3OwnedObject, IW3AssetAccess) private FState: TW3AssetState; FCanceled: boolean; FIdentifier: TW3AssetIdentifier; protected procedure BeforeAssetIO;virtual; procedure AfterAssetIO;virtual; procedure FailedAssetIO;virtual; procedure ResetData;virtual; procedure SetState(const NewState:TW3AssetState); procedure SetIdentifier(const NewIdentifier:TW3AssetIdentifier); procedure SetData(const Value: variant); protected function ManagerAccess: IW3AssetManagerAccess; procedure PerformIO(const ThisURI: String; OnReady: TW3AssetReadyEvent; OnFailed: TW3AssetIOFailureEvent);virtual; public Property Owner: TW3AssetManager read ( TW3AssetManager(inherited Owner) ); Property URI: String; Property Data: TW3AssetData; Property State: TW3AssetState read FState; property Identifier: TW3AssetIdentifier read FIdentifier; procedure Execute;virtual; procedure Cancel;virtual; constructor Create(const AOwner: TW3AssetManager);reintroduce; destructor Destroy;override; published Property OnBeforeIO: TW3AssetBeforeIOEvent; property OnExecuting: TW3AssetIOEvent; property OnAssetReady: TW3AssetReadyEvent; property OnAssetFailed: TW3AssetIOFailureEvent; end; TW3AssetManager = class(TObject, IW3AssetManagerAccess) private FAssets: array of TW3CustomAsset; FRemaining: integer; FState: TW3AssetManagerState; protected procedure AssetReady(const Asset:TW3CustomAsset);virtual; procedure AssetFailed(const Asset:TW3CustomAsset);virtual; function GetAssetById(const AssetId: TW3AssetIdentifier): TW3CustomAsset; public property Active: boolean read ( (FRemaining>0) and (FState=msLoading) ); property State: TW3AssetManagerState read FState; Property RemainingToLoad: Integer read FRemaining; property ReadyToUse: integer read (FAssets.Count - FRemaining); property Assets[const Id:string]:TW3CustomAsset read GetAssetById; property Items[const AssetIndex: integer]:TW3CustomAsset read (FAssets[AssetIndex]); property Count: integer read (FAssets.Count); function Add(const AssetObject: TW3CustomAsset): TW3CustomAsset; procedure LoadAllAssets; procedure Cancel; procedure Reset; procedure Clear; destructor Destroy;override; end;
Then we have codecs for dealing with ciphers and conversion in a unified way, and much, much more. But you probably want to see the new node.js high level classes right? Well here is little taste
TW3NodeFileSystem = class(TW3CustomFileSystem) public function Examine(const FullPath: String;var Files: TStrArray):boolean; override; function Rename(const OldPath, NewPath: String): boolean;override; function FileExists(const Filename: string): boolean;override; function DirectoryExists(const FullPath: string): boolean;override; function QueryFilePermissions(const FullPath: string;var Permissions:TW3FilePermissionMask): boolean;override; function CreateDirectory(const FullPath: string; Permissions: TW3FilePermissionMask): boolean;override; function DeleteDirectory(const FullPath: string): boolean;override; function QueryFilesize(const Filename: string;var Size:integer):Boolean;override; procedure LoadBinaryFile(const Filename: string; var Binary: TBinaryData);override; procedure LoadTextFile(const Filename: string; var Text: string);override; procedure LoadStreamFile(const Filename: string; var Stream: TStream);override; procedure SaveBinaryFile(const Filename: string; const Binary: TBinaryData);override; procedure SaveTextFile(const Filename: string; const Text: string);override; procedure SaveStreamFile(const Filename: string; const Stream: TStream);override; procedure ExamineA(const FullPath: String; const CallBack: TW3FileSystemExamineHandler); procedure RenameA(const OldPath, NewPath: String; const CallBack: TW3FileSystemErrorHandler); procedure FileExistsA(const Filename: string; const Callback: TW3FileSystemExistsHandler); procedure DirectoryExistsA(const FullPath: string; const Callback: TW3FileSystemExistsHandler); procedure CreateDirectoryA(const FullPath: string; Permissions: TW3FilePermissionMask; const CallBack: TW3FileSystemErrorHandler); procedure DeleteDirectoryA(const FullPath: string; const CallBack: TW3FileSystemErrorHandler); end; TNJServerAfterStartedEvent = procedure (sender: TObject); TNJServerAfterStoppedEvent = procedure (sender: TObject); TNJServerBeforeStartEvent = procedure (sender: TObject); TNJServerBeforeStopEvent = procedure (sender: TObject); TNJHandleBasedClass = class(TObject) private FHandle: THandle; protected procedure SetHandle(const NewHandle: THandle); virtual; function GetHandle: THandle; virtual; public property Handle: THandle read GetHandle; end; TNJServerChildClass = class(TNJHandleBasedClass) private FParent: TNJCustomServer; public property Server: TNJCustomServer read FParent; constructor Create(Server: TNJCustomServer); virtual; end; TNJCustomServerRequest = class(TNJServerChildClass) end; TNJCustomServerResponse = class(TNJServerChildClass) end; TNJCustomServer = class(TObject) private FHandle: THandle; FActive: boolean; FPort: integer; protected procedure BeforeStart;virtual; procedure AfterStart;virtual; procedure BeforeStop;virtual; procedure AfterStop;virtual; protected procedure StartServer;virtual; procedure StopServer;virtual; function GetActive: boolean;virtual; procedure SetActive(const Value: boolean);virtual; function GetHandle: THandle; virtual; procedure SetHandle(const Value: THandle); virtual; function GetPort: integer;virtual; procedure SetPort(const Value: integer);virtual; public property OnAfterServerStarted: TNJServerAfterStartedEvent; property OnAfterServerStopped: TNJServerAfterStoppedEvent; property OnBeforeServerStarted: TNJServerBeforeStartEvent; property OnBeforeServerStopped: TNJServerBeforeStopEvent; property Active: boolean read GetActive write SetActive; property Port: integer read GetPort write SetPort; procedure Start;virtual; procedure Stop;virtual; end; // Base exception for HTTP server(s) ENJHttpServerError = class(ENJServerError); // Http request-object TNJHttpRequest = class(TNJCustomServerRequest) private FEncoding: string; protected function GetHeaders: TJsonObject;virtual; function GetTrailers: TJsonObject;virtual; procedure SetEncoding(const Value: string); virtual; function GetEncoding: string; virtual; public property Socket: JNodeSocket read ( JServerRequest(Handle).connection ); property Encoding: string read GetEncoding write SetEncoding; property &Method: string read ( Handle.&method ); property Url: string read ( Handle.url ); property Headers: TJsonObject read GetHeaders; property Trailers: TJsonObject read GetTrailers; property HttpVersion: string read ( Handle. httpVersion ); procedure Pause; virtual; procedure Resume; virtual; constructor Create(const Server: TNJCustomServer; const RequestObject: JServerRequest); reintroduce; virtual; end; // Http response-object TNJHttpResponse = class(TNJCustomServerRequest) protected function GetSendDate: boolean; virtual; procedure SetSendDate(const Value: boolean);virtual; public property StatusCode: integer read (Handle.statusCode) write (Handle.statusCode); property SendDate: boolean read GetSendDate write SetSendDate; function GetHeader(const Name: string): string; procedure SetHeader(const Name: string; const Value: string); procedure RemoveHeader(const Name: string); function Write(const Text: string): boolean;overload; function Write(const Text: string; const Encoding: string): boolean;overload; procedure Write(const Stream: TStream); overload; procedure Write(const Buffer: TBinaryData); overload; procedure &End(const Buffer: TBinaryData); overload; procedure &End(const Data: variant); overload; procedure &End(const Data: variant; const Encoding: string); overload; procedure &End(const Stream: TStream);overload; procedure &End(const Stream: TStream; const Encoding: string); overload; constructor Create(const Server: TNJCustomServer; const ResponseObject: JServerResponse); reintroduce; virtual; end; TNJHTTPServerRequestEvent = procedure (Sender: TObject; const Request: TNJHttpRequest; const Response: TNJHttpResponse); TNJHTTPServer = class(TNJCustomServer) private procedure InternalSetActive(const Value: boolean); protected procedure Dispatch(request: JServerRequest; response: JServerResponse); virtual; protected procedure SetActive(const Value: boolean);override; procedure StartServer;override; procedure StopServer;override; public property Instance: JServer read ( JServer(GetHandle)); published property OnRequest: TNJHTTPServerRequestEvent; end; TNJWebSocketSocket = class(TObject) private FSocket: JWsSocket; FServer: TNJWebSocketServer; protected function GetReadyState: JWsReadyState; virtual; function GetSocketName: string; virtual; function GetSocketRequest: TNJHttpRequest; virtual; public property ReadyState: JWsReadyState read GetReadyState; property Name: string read GetSocketName; property Server: TNJWebSocketServer read FServer; property Request: TNJHttpRequest read GetSocketRequest; property OnSocketClose: TNJWebSocketSocketNotifyEvent; property OnSocketOpen: TNJWebSocketSocketNotifyEvent; property OnMessage: TNJWebSocketSocketMessageEvent; procedure Emit(EventName: string; Data: variant; Attempts: integer); constructor Create(const Server: TNJWebSocketServer; const WsSocket: JWsSocket); virtual; destructor Destroy;override; end; TNJWebSocketServerTextMessageEvent = procedure (const Sender: TNJWebSocketServer; const Info: TNJWebsocketMessageInfo); TNJWebSocketServerBinaryMessageEvent = procedure (const Sender: TNJWebSocketServer; const Info: TNJWebsocketMessageInfo); TNJWebSocketServerClientConnected = procedure (const Sender: TNJWebSocketServer; const Socket: JWsSocket); TNJWebSocketServerClientDisconnected = procedure (const Sender: TNJWebSocketServer; const Socket: JWsSocket); TNJWebSocketOnHandler = procedure (const Socket: JWsSocket; const Data: variant); TNJWebSocketServer = class(TNJCustomServer) private FOnConnected: TNJWebSocketServerClientConnected; FOnDisconnected: TNJWebSocketServerClientDisconnected; FOnTextMessage: TNJWebSocketServerTextMessageEvent; FOnBinMessage: TNJWebSocketServerBinaryMessageEvent; FTrack: boolean; FPath: string; procedure InternalSetActive(const Value: boolean); protected procedure SetPath(URLPath: string); virtual; procedure SetTracking(Value: boolean); virtual; protected procedure Dispatch(const Data: TNJWebsocketMessageInfo); virtual; procedure SetActive(const Value: boolean);override; procedure StartServer;override; procedure StopServer;override; public property Path: string read FPath write SetPath; property ClientTracking: boolean read FTrack write SetTracking; procedure Emit(EventName: string; Data: variant; Attempts: integer); procedure On(EventName: string; CallBack: TNJWebSocketOnHandler); published property OnClientConnected: TNJWebSocketServerClientConnected read FOnConnected write FOnConnected; property OnClientDisconnected: TNJWebSocketServerClientDisconnected read FOnDisconnected write FOnDisconnected; property OnTextMessage: TNJWebSocketServerTextMessageEvent read FOnTextMessage write FOnTextMessage; property OnBinMessage: TNJWebSocketServerBinaryMessageEvent read FOnBinMessage write FOnBinMessage; end;
I could do this all day. So yeah, there is going to be a few changes. A lot of changes. And you will be amazed at how much cool stuff you can do! While I love all the cool new stuff thats going into SmartCL (the visual application types) I must admit that working with node.js and IOT is what really gives me a kick these days. When it takes less than 1 minute to write a server that can scale, execute in fork or cluster mode and daisy-chained – all of it while making use of the latest technology and API’s — then you have some firepower to play with.
So stay tuned for more, much more 🙂
You must be logged in to post a comment.