Archive

Archive for July, 2014

Calculating text-metrics with Smart Mobile Studio

July 20, 2014 2 comments

When creating your own custom controls, be they composite controls (built using other components) or purely graphical controls, calculating the width and height of text is important.

Here is a small class I wrote to deal with that topic. Its doesn’t calculate accent and decent, but that could be extracted by comparing to an empty SPAN or DIV. You may also want to reset padding and margin, depending on your stylesheet (so your style doesnt override the default values of the calculated data). Tested in Chrome (webkit).


type

  TQTXTextMetric  = Record
    tmWidth:  Integer;
    tmHeight: Integer;
    function  toString:String;
  End;

  TQTXControlTools = Class
  public
    class function calcTextMetrics(const aText:String;
          const aFontName:String;const aFontSize:Integer):TQTXTextMetric;

    class function calcTextAverage(const aFontName:String;
          const aFontSize:Integer):TQTXTextMetric;
  end;


function TQTXTextMetric.toString:String;
Begin
  result:=Format('width=%d px, height=%d px',[tmWidth,tmHeight]);
end;

class function TQTXControlTools.calcTextAverage(const aFontName:String;
      const aFontSize:Integer):TQTXTextMetric;
Begin
  result:=calcTextMetrics('gWÅzj§',afontName,aFontSize);
end;

class function TQTXControlTools.calcTextMetrics(const aText:String;
  const aFontName:String;const aFontSize:Integer):TQTXTextMetric;
var
  mHandle:  THandle;
Begin
  asm
    @mHandle = document.createElement("span");
  end;

  mHandle.style['display']:='inline-block';
  mHandle.style['visibility']:='hidden';
  mHandle.style['font-family']:=aFontName;
  mHandle.style['font-size']:=TInteger.ToPxStr(aFontSize);

  mhandle.textContent := aText;

  asm
    document.body.appendChild(@mHandle);
  end;

  result.tmHeight := mHandle.offsetHeight;
  result.tmWidth  := mHandle.offsetWidth;

  asm
    document.body.removeChild(@mHandle);
  end;
end;

Discount only 1 week to go!

July 12, 2014 Leave a comment
Create awesome JavaScript code in Object Pascal

Write awesome browser code with Smart Pascal

The fantastic $99 and $199 discount for Smart Mobile Studio professional and enterprise is about to expire! Only one week left of this extreme discount — so no time to waste!

You will get your license just in time for the next major update — so this is your chance to get the de-facto, #1 object pascal to JavaScript compiler at a massive 50% discount (!)

In short — the discount expires Sunday 20’th of Juli

How do I order?

Simple. Head over to http://www.smartmobilestudio.com and go to our order page. Remember to mark your order with “JLA” which is the discount code word. And that’s it.

You get all the updates for a whole year (and that is a very, very good deal).

Read the original discount post here.

Why is this cool?

Smart Pascal is the natural Delphi replacement for the web. It has also been called “A complete Adobe Flash replacement” and “What HTML5 Builder should have been”. You can use it to write mobile applications that run on your iOS or Android devices, interface with your native Delphi databases or back-end servers — and even to control hardware that run JavaScript (yes, more and more embedded boards and micro-controllers run JavaScript).

Leverage your Object Pascal knowledge for the next generation, HTML5 based application development!

Why spend six months writing elementary JavaScript – when you can do it in 6 days using Smart Pascal? Fully object-oriented, inheritance, RTTI, interfaces — all the benefits of a modern, high-level language running in your browsers.

No dependencies. No limits — just pure Smart Pascal!

Dataset for Smart Mobile Studio – Update

July 10, 2014 Leave a comment

Yesterday I posted a preliminary dataset class for Smart Mobile Studio which is perfect for client-side HTML5 development. I have since spent a couple of hours more working on the unit, and here is the update. Still a few tweaks here and there, but in general it’s now usable, stable and persistent (!). This was a very interesting and rewarding task 🙂

Some of the updates since my last post:

  • Schema is now defined in FieldDef classes (just like Delphi)
  • Calculated (read: generated) fields are now supported
    • Autoinc field class added
    • GUID field class added
  • Field-Definition IO in JSON format is in place
  • Dataset can now load and save to JSON format as well
  • Added Append() method for inserting record at the end of a dataset
  • Altered Insert() method, allowing for insertion at any position (except -1 [BOF] and Count+1 [EOF]).
  • Added MoveTo() method for absolute navigation
  • Further abstracted architecture
  • Exception handling in place
  • More events (and more to come)
  • Cleaned up source and re-factored quite a few procs

About the GUID calculated field

Javascript doesnt support GUID’s directly. There is no CreateGUID() or GUIDToString() provided by the browser. But thankfully the modern RFC for GUID generation allows for random numbers. So the GUID field is simply an auto-generated GUID string. If you for some reason want to avoid integer autoinc identifiers – but need unique field values (non editable) then TGUIDField is your class.

Note: In retrospect it makes sense to rename these fields to “generated fields”, since calculated fields are event based and user-definable. But I am still in the prototype stage when it comes to this code, so bear with me while I finish it.

How to use it

The dataset follows the ordinary BOF/EOF style navigation. Meaning that BOF (beginning of file) is at position -1 rather than zero, while EOF (end of file) is Count+1. The record pointer self-adjusts if you attempt an insert at these points. Where EOF results in an append, while insert(BOF) adds a record at the top of the dataset (position zero).

Other than that, it’s fairly straight shooting.

If you are going to have a global dataset in your Smart Pascal app’s, the best place to create it is in the project source – as a part of your project’s TApplication instance. All Smart Pascal applications have a unique TApplication object (not static like under Delphi), so this is the perfect place to initialize the dataset instance. Then you can typecast from any form to reach it (e.g: TApplication(application).Dataset).

Speed

It’s not going to make Bill Gates lose sleep any time soon, but I was surprised at how fast it works. This has more to do with the fact that it functions more like an array of variants, where each variant represents a JSON object (array of named values) than anything else. 1000 Inserts of the example below takes less than 1 second. It’s hard to measure it since it hardly makes a dent in the tick-counter.

So all in all it’s perfectly suited for Smart Mobile Studio projects. And when you compile with phonegap it’s even faster since the latest Cordova extensions for iOS boosts array operations considerably.

Procedure TForm1.TestDataset;
var
  mDataset: TW3CustomDataset;
  x:Integer;
  mId: Integer;
begin
  mDataset:=TW3CustomDataset.Create;
  try
    mDataset.FieldDefs.add('id',ftAutoInc);
    mDataset.FieldDefs.add('key',ftGUID);
    mDataset.FieldDefs.add('name',ftString);
    mDataset.createDataset;

    for x:=1 to 50 do
    Begin
      mDataset.Append;
      mId:=mDataset.Fields.FieldByName('id').asInteger;
      mDataset.Fields.FieldByName('name').asString:='Data field #' + IntToStr(mId);
      mDataset.post;
    end;

    showmessage(mDataset.saveToString);

  finally
    mDataset.free;
  end;
end;
Saving a dataset to JSON is now a snap

Saving a dataset to JSON is now a snap

 

unit w3dataset;

//#############################################################################
//
//  Unit:       w3dataset.pas
//  Author:     Jon Lennart Aasenden
//  Copyright:  Jon Lennart Aasenden, all rights reserved
//
//  Description:
//  ============
//  Implements a small and fast in-memory dataset. Supports all the basic
//  dataset operations (including autoinc field type) but no SQL and
//  as of writing, no filtering scheme or regEx support.
//
//
//  Cipher Diaz of QUARTEX
//
//#############################################################################

interface

uses
  W3System;

{.$DEFINE USE_DEVCODE}

type

  (* Forward declaration *)
  TW3CustomDatasetField = Class;
  TW3BooleanField       = Class;
  TW3IntegerField       = Class;
  TW3FloatField         = Class;
  TW3StringField        = Class;
  TW3DatasetFields      = Class;
  TW3CustomDataset      = Class;
  TW3FieldDef           = Class;
  TW3FieldDefs          = Class;

  (* Exception classes *)
  EW3FieldDefs          = Class(EW3Exception);
  EW3DatasetField       = Class(EW3Exception);
  EW3Dataset            = Class(EW3Exception);

  TW3DatasetPacket      = Variant;

  TW3DatasetFieldType =
    (
        ftUnknown,
        ftBoolean,
        ftInteger,
        ftFloat,
        ftString,
        ftDateTime,
        ftAutoInc,    //  Calculated
        ftGUID        //  Calculated
    );

  TW3DatasetState =(dsIdle,dsInsert,dsEdit);

  IDatasetFieldsAccess  = Interface
    Procedure  ResetValues;
    Procedure  setReadOnly(const aValue:Boolean);
  end;

  IFieldDefAccess = interface
    Procedure SetReadOnly(Const aValue:Boolean);
    function  getFieldIdentifier:String;
  end;

  TW3GUID = Class
  public
    class function CreateGUID:String;
  end;

  TW3CustomDatasetField = Class(TObject)
  private
    FName:      String;
    FKind:      TW3DatasetFieldType;
    FReadOnly:  Boolean;
    FValue:     Variant;
    FParent:    TW3DatasetFields;
    Procedure   setAsDateTime(const aValue:TDateTime);
    function    getAsDateTime:TDateTime;
  protected
    Procedure   Calculate;virtual;
    function    getCalculated:Boolean;virtual;
    function    getValue:Variant;virtual;
    procedure   setValue(const aValue:variant);virtual;
    procedure   setName (const aValue:String);
    procedure   setKind (const aKind:TW3DatasetFieldType);
    Procedure   setReadOnly(const aValue:Boolean);
  public
    Property    Parent:TW3DatasetFields read FParent;
    Property    Kind:TW3DatasetFieldType read FKind;
    Property    Name:String read FName write setName;
    Property    Data:Variant read getValue write setValue;
    Property    Calculated:Boolean read getCalculated;

    Property    AsString:String
                read (TVariant.AsString(FValue))
                write (FValue:=Value);

    Property    AsInteger:Integer
                read (TVariant.asInteger(FValue))
                write (FValue:=Value);

    Property    AsBoolean:Boolean
                read (TVariant.AsBool(FValue))
                write (FValue:=Value);

    Property    AsFloat:Float
                read (TVariant.asFloat(FValue))
                write (FValue:=Value);

    Property    AsDateTime:TDateTime
                read  getAsDateTime
                write setAsDateTime;

    Constructor Create(const aParent:TW3DatasetFields);virtual;
  End;

  TW3AutoIncField = Class(TW3CustomDatasetField)
  private
    FCalc:    Integer;
  protected
    Procedure Calculate;override;
    function  getCalculated:Boolean;override;
  public
    Property  Value:Integer
              read (TVariant.AsInteger(Inherited getValue));
  End;

  TW3GUIDField = Class(TW3CustomDatasetField)
  protected
    Procedure Calculate;override;
    function  getCalculated:Boolean;override;
  public
    Property  Value:String
              read (TVariant.asString(Inherited getValue));
  End;

  TW3BooleanField = Class(TW3CustomDatasetField)
  public
    Property  Value:Boolean
              read (TVariant.AsBool(Inherited getValue))
              write (inherited setValue(Value));
  End;

  TW3IntegerField = Class(TW3CustomDatasetField)
  public
    Property  Value:Integer
              read (TVariant.AsInteger(Inherited getValue))
              write (inherited setValue(Value));
  end;

  TW3FloatField = Class(TW3CustomDatasetField)
  public
    Property  Value:Float
              read (TVariant.AsFloat(Inherited getValue))
              write (inherited setValue(Value));
  end;

  TW3StringField = Class(TW3CustomDatasetField)
  public
    Property  Value:String
              read (TVariant.AsString(Inherited getValue))
              write (inherited setValue(Value));
  end;

  TW3DateTimeField = Class(TW3CustomDatasetField)
  protected
    function  getValue:TDateTime;reintroduce;
    procedure setValue(const aValue:TDateTime);reintroduce;
  public
    Property  Value:TDateTime read getValue write setValue;
  End;

  TW3DatasetFields = Class(TObject,IDatasetFieldsAccess)
  private
    FFields:    Array of TW3CustomDatasetField;
  protected
    (* IMPLEMENTS:: IDatasetFieldsAccess *)
    Procedure   ResetValues;
    Procedure   setReadOnly(const aValue:Boolean);
  public
    Property    Count:Integer read (FFields.count);
    Property    Items[index:Integer]:TW3CustomDatasetField read (FFields[index]);
    function    DataExport:TW3DatasetPacket;virtual;
    Procedure   DataImport(const aValue:TW3DatasetPacket);virtual;
    function    IndexOf(aName:String):Integer;
    function    Add(aName:String;
                const aKind:TW3DatasetFieldType):TW3CustomDatasetField;
    Procedure   DeleteByName(aName:String);
    Procedure   Delete(const aIndex:Integer);
    Procedure   Clear;
    function    FieldByName(aName:String):TW3CustomDatasetField;
    Destructor  Destroy;Override;
  End;

  TW3FieldDef = Class(Tobject)
  private
    FDatatype:  TW3DatasetFieldType;
    FName:      String;
    FReadOnly:  Boolean;
    FParent:    TW3FieldDefs;
    Procedure   setType(const aValue:TW3DatasetFieldType);
    Procedure   setName(const aName:String);
  protected
    Procedure   setReadOnly(const aValue:Boolean);
  public
    Property    Name:String read FName write setName;
    Property    Datatype:TW3DatasetFieldType read FDatatype write setType;
    Constructor Create(Const aParent:TW3FieldDefs);virtual;
  End;

  TW3FieldDefs = Class(TObject,IFieldDefAccess)
  private
    FFields:    Array of TW3FieldDef;
    FId:        Integer;
    FReadOnly:  Boolean;
  protected
    function    getFieldIdentifier:String;virtual;
    Procedure   SetReadOnly(Const aValue:Boolean);
  public
    Property    ReadOnly:Boolean read FReadOnly;
    Property    Fields[index:Integer]:TW3FieldDef
                read (FFields[index]);default;

    Property    Count:Integer read (FFields.Count);

    function    Add(aName:String;
                const aDataType:TW3DatasetFieldType):TW3FieldDef;

    function    FieldByName(aName:String):TW3FieldDef;
    Function    IndexOf(aName:String):Integer;
    Procedure   Delete(const aIndex:Integer);overload;
    procedure   Delete(const aItem:TW3FieldDef);overload;

    function    SaveToString:String;
    Procedure   LoadFromString(const aText:String);

    function    toString:String;virtual;

    Procedure   Clear;
    Destructor  Destroy;Override;
  End;

  TDatasetStateChangeEvent = Procedure (sender:TObject;const aState:TW3DatasetState);
  TDatasetPositionChangeEvent = procedure (sender:TObject;aOldPos,aNewPos:Integer);
  TDatasetRecordDeleteEvent   = Procedure (sender:TObject;const aRecNo:Integer);

  TW3CustomDataset = Class(TObject)
  private
    FFields:    TW3DatasetFields;
    FDefs:      TW3FieldDefs;
    FCache:     Array of TW3DatasetPacket;
    FState:     TW3DatasetState;
    FActive:    Boolean;
    FDestroying:Boolean;

    FOnCreated: TNotifyEvent;
    FOnClosed:  TNotifyEvent;
    FOnState:   TDatasetStateChangeEvent;
    FOnPos:     TDatasetPositionChangeEvent;
    FOnDelete:  TDatasetRecordDeleteEvent;

    FDsIndex:   Integer;

    Procedure   UpdateCalculatedFields;

    procedure   setActive(const aValue:Boolean);
    Procedure   setPosition(const aNewPosition:Integer);
  protected
    Procedure   DoBeforeDatasetCreated;virtual;
    Procedure   DoAfterDatasetCreated;virtual;
    Procedure   DoBeforeDatasetClosed;virtual;
    Procedure   DoAfterDatasetClosed;virtual;

    function    getRecCount:Integer;virtual;
    function    getRecNo:Integer;virtual;
    function    getEOF:Boolean;virtual;
    function    getBOF:Boolean;virtual;

    procedure   getPacketToFields;virtual;
    Procedure   setPacketFromFields;virtual;

    procedure   setState(const aState:TW3DatasetState);
  public
    Property    Active:Boolean read FActive write setActive;
    Property    State:TW3DatasetState read FState;
    Property    Fields:TW3DatasetFields read FFields;
    Property    FieldDefs:TW3FieldDefs
                read FDefs;

    Property    EOF:Boolean read getEOF;
    Property    BOF:Boolean read getBOF;
    Property    Count:Integer read getRecCount;
    Property    RecNo:Integer read getRecNo;

    // Save and Load to JSON format
    function    SaveToString:String;
    Procedure   LoadFromString(Const aText:String);

    Procedure   Append; //Add record LAST always
    procedure   Insert; //Add record at position
    Procedure   Delete; //Delete record at position
    Procedure   Post;   //Complete insertion or update
    procedure   Edit;   //Edit record at position

    Procedure   Next;   //Navigate 1 step forward
    Procedure   Back;   //Navigate 1 step back
    Procedure   First;  //Navigate to first record
    Procedure   Last;   //Navigate to last record
    Procedure   MoveTo(const aRecNo:Integer); //Navigate directly

    Procedure   CreateDataset;
    Procedure   Close;

    Procedure   Cancel;

    class function Version:String;

    Constructor Create;virtual;
    Destructor  Destroy;Override;
  published

    Property    OnRecordDeleted:TDatasetRecordDeleteEvent
                read FOnDelete write FOnDelete;

    Property    OnDatasetCreated:TNotifyEvent read FOnCreated write FOnCreated;
    Property    OnDatasetClosed:TNotifyevent read FOnClosed write FOnClosed;

    Property    OnStateChanged:TDatasetStateChangeEvent
                read FOnState write FOnState;

    Property    OnPositionChanged:TDatasetPositionChangeEvent
                read FOnPos write FOnPos;
  end;

  {$IFDEF USE_DEVCODE}
  TW3MemoryDataset = class(TW3CustomDataset)
  private
    FCache:     Array of TW3DatasetPacket;
  protected
    procedure   getPacketToFields;override;
    Procedure   setPacketFromFields;override;
    function    getRecCount:Integer;override;
    Procedure   DoBeforeDatasetClosed;override;
  End;
  {$ENDIF}

  TW3DataSource = Class(TObject)
  end;

implementation

const
CNT_DATASET_MAJOR = 0;
CNT_DATASET_MINOR = 1;

resourcestring
CNT_DATASET_FIELD_READONLY  = 'Failed to alter value, field is read-only error';
CNT_DATASET_NOT_ACTIVE      = 'Operation failed, dataset is not active error';
CNT_DATASET_INVALID_STATE   = 'Invalid state for operation error';
CNT_DATASET_FIELD_UNKNOWN   = 'Failed to match field class for datatype error';

CNT_DATASET_FIELDEF_LIVE    = 'Field definition cannot be altered in a live dataset error';

//#############################################################################
// Internal records used for storage etc.
//#############################################################################

type

TW3FieldDefData = Record
  fdName: String;
  fdDatatype: TW3DatasetFieldType;
End;

TW3FieldDefHeader = Record
  ddMagic:  Integer;
  ddDefs:   Array of TW3FieldDefData;
End;

TW3DatasetHeader  = Record
  dhMagic:      Integer;
  dhCount:      Integer;
  dhFieldDefs:  String;
  dhData:       String;
End;

//#############################################################################
// TW3GUID
//#############################################################################

// http://www.ietf.org/rfc/rfc4122.txt
class function TW3GUID.CreateGUID:String;
Begin
  asm
    var s = [];
    var hexDigits = "0123456789abcdef";
    for (var i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    }
    s[14] = "4";
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
    s[8] = s[13] = s[18] = s[23] = "-";

    @result = s.join("");
  end;
  result:=uppercase(result);
end;

//#############################################################################
// TW3MemoryDataset
//#############################################################################
{$IFDEF USE_DEVCODE}
procedure TW3MemoryDataset.getPacketToFields;
Begin
  if (RecNo>=0) and (RecNo<FCache.Length) then
  Fields.DataImport(FCache[RecNo]);
end;

Procedure TW3MemoryDataset.setPacketFromFields;
Begin
  if (RecNo>=0) and (RecNo<FCache.Length) then
  FCache[RecNo]:=Fields.DataExport;
end;

function TW3MemoryDataset.getRecCount:Integer;
Begin
  result:=FCache.Count;
end;

Procedure TW3MemoryDataset.DoBeforeDatasetClosed;
Begin
  FCache.Clear;
  inherited;
end;
{$ENDIF}
//#############################################################################
// TW3FieldDefs
//#############################################################################

Destructor TW3FieldDefs.Destroy;
begin
  if FFields.Count>0 then
  Clear;
  inherited;
end;

Procedure TW3FieldDefs.clear;
var
  x:  Integer;
begin
  if FFields.Count>0 then
  Begin
    try
      for x:=FFields.low to FFields.high do
      FFields[x].free;
    finally
      FFields.clear;
    end;
  end;
end;

function TW3FieldDefs.getFieldIdentifier:String;
Begin
  repeat
    inc(FId);
    result:='Field' + FId.toString;
  until IndexOf(result)<0;
end;

Procedure TW3FieldDefs.SetReadOnly(Const aValue:Boolean);
var
  mItem:  TW3FieldDef;
Begin
  FReadOnly:=aValue;
  if FFields.length>0 then
  for mItem in FFields do
  mItem.setReadOnly(aValue);
end;

function  TW3FieldDefs.Add(aName:String;
          const aDataType:TW3DatasetFieldType):TW3FieldDef;
Begin
  result:=NIL;
  if not FReadOnly then
  begin
    aName:=trim(lowercase(aName));
    if aName.length>0 then
    Begin
      if indexOf(aName)<0 then
      Begin
        result:=TW3FieldDef.Create(self);
        result.name:=aName;
        result.datatype:=aDataType;
        FFields.add(result);
      end else
      raise EW3FieldDefs.Create('A field with that name already exists error');
    end else
    raise EW3FieldDefs.create('Failed to add field definition, invalid name error');
  end else
  raise EW3FieldDefs.create(CNT_DATASET_FIELDEF_LIVE);
end;

function  TW3FieldDefs.FieldByName(aName:String):TW3FieldDef;
var
  x:  Integer;
Begin
  result:=NIL;
  if FFields.Length>0 then
  begin
    aName:=lowercase(trim(aName));
    if length(aName)>0 then
    begin
      for x:=FFields.low to FFields.high do
      Begin
        if aName = lowercase(FFields[x].name) then
        begin
          result:=FFields[x];
          break;
        end;
      end;
    end;
  end;
end;

Function  TW3FieldDefs.IndexOf(aName:String):Integer;
var
  x:  Integer;
Begin
  result:=-1;
  if FFields.Length>0 then
  begin
    aName:=lowercase(trim(aName));
    if length(aName)>0 then
    begin
      for x:=FFields.low to FFields.high do
      Begin
        if aName = lowercase(FFields[x].name) then
        begin
          result:=x;
          break;
        end;
      end;
    end;
  end;
end;

Procedure TW3FieldDefs.Delete(const aIndex:Integer);
Begin
  if not FReadOnly then
  begin
    If (aIndex>=0) and (aIndex<FFields.length) then
    Begin
      FFields[aIndex].free;
      FFields.Delete(aIndex,1);
    end;
  end else
  raise EW3FieldDefs.create(CNT_DATASET_FIELDEF_LIVE);
end;

procedure TW3FieldDefs.Delete(const aItem:TW3FieldDef);
Begin
  if aItem<>NIL then
  Delete(IndexOf(aItem.Name)) else
  raise EW3FieldDefs.create('Delete operation failed, reference was NIL error');
end;

function TW3FieldDefs.SaveToString:String;
var
  x:  Integer;
  mHead:  TW3FieldDefHeader;
Begin
  mHead.ddMagic:=$BABE;
  if FFields.Count>0 then
  Begin
    mHead.ddDefs.SetLength(FFields.Count);
    for x:=FFields.low to FFields.high do
    Begin
      mHead.ddDefs[x].fdDatatype:=FFields[x].dataType;
      mHead.ddDefs[x].fdName:=FFields[x].Name;
    end;
  end;
  asm
    @result = JSON.stringify(@mHead);
  end;
end;

Procedure TW3FieldDefs.LoadFromString(Const aText:String);
var
  mHead:  TW3FieldDefHeader;
  x:  Integer;
Begin
  Clear;

  try
    asm
      @mHead = JSON.parse(@aText);
    end;
  except
    On e: exception do
    Raise EW3FieldDefs.CreateFmt
    ('Failed to load field-definitions, system threw exception: %s',[e.message]);
  end;

  if mHead.ddMagic=$BABE then
  Begin
    if mHead.ddDefs.count>0 then
    begin
      for x:=mHead.ddDefs.low to mHead.ddDefs.high do
      Add(mHead.ddDefs[x].fdName,mHead.ddDefs[x].fdDatatype);
    end;
  end else
  Raise EW3FieldDefs.Create('Failed to load field-definitions, invalid header signature error');
end;

function  TW3FieldDefs.toString:String;
var
  x:  Integer;
Begin
  if FFields.Count>0 then
  Begin
    for x:=FFields.low to FFields.high do
    Begin
      result+='Name=' + '"' + FFields[x].Name + '"' + ' Datatype=';
      case FFields[x].Datatype of
      ftUnknown:  result+='Unknown';
      ftBoolean:  result+='Boolean';
      ftInteger:  result+='Integer';
      ftFloat:    result+='Float';
      ftString:   result+='String';
      ftDateTime: result+='DateTime';
      ftAutoInc:  result+='AutoInc';
      ftGUID:     result+='GUID';
      end;
      result:=result + #13;
    end;
  end;
end;

//#############################################################################
// TW3FieldDef
//#############################################################################

Constructor TW3FieldDef.Create(Const aParent:TW3FieldDefs);
Begin
  inherited Create;
  if aParent<>NIL then
  begin
    FParent:=aParent;
    FName:=(FParent as IFieldDefAccess).getFieldIdentifier;
  end;
end;

Procedure TW3FieldDef.setReadOnly(const aValue:Boolean);
Begin
  FReadOnly:=aValue;
end;

Procedure TW3FieldDef.setType(const aValue:TW3DatasetFieldType);
begin
  if not FReadOnly then
  FDatatype:=aValue;
end;

Procedure TW3FieldDef.setName(const aName:String);
Begin
  if not FReadOnly then
  Begin
    FName:=aName;
  end;
end;

//#############################################################################
// TW3CustomDataset
//#############################################################################

Constructor TW3CustomDataset.Create;
Begin
  inherited Create;
  FFields:=TW3DatasetFields.Create;
  FDefs:=TW3FieldDefs.Create;
  FState:=dsIdle;
  FDestroying:=False;
  FDsIndex:=-1;
end;

Destructor  TW3CustomDataset.Destroy;
Begin
  FDestroying:=true;

  if FActive then
  Close;

  FFields.free;
  FDefs.free;

  inherited;
end;

Class function TW3CustomDataset.Version:String;
Begin
  result:=IntToStr(CNT_DATASET_MAJOR) + '.' + IntToStr(CNT_DATASET_MINOR);
end;

Function TW3CustomDataset.saveToString:String;
var
  mHead:  TW3DatasetHeader;
Begin
  if FActive then
  Begin
    try
      (* Setup the header *)
      mHead.dhMagic:=$CAFE;
      mHead.dhCount:=getRecCount;

      (* Serialize and store field-defs *)
      mHead.dhFieldDefs:=EncodeURI(FDefs.SaveToString);

      (* Serialize and store dataset records *)
      asm
        (@mHead).dhData = JSON.stringify((@self).FCache);
      end;

      (* Now serialize and return text representation of data structure *)
      asm
        @result = JSON.stringify(@mHead);
      end;

    except
      on e: exception do
      raise EW3Dataset.CreateFmt
      ('Failed to store dataset, system threw exception: %s',[e.message]);
    end;

  end;
end;

Procedure TW3CustomDataset.LoadFromString(Const aText:String);
var
  mHead:  TW3DatasetHeader;
Begin
  (* If the dataset is active, close it down *)
  if FActive then
  Close;

  (* Check source string *)
  if aText.Length>0 then
  Begin
    (* Attempt to de-serialize JSON data *)
    try
      asm
        @mHead = JSON.parse(@aText);
      end;
    except
      on e: exception do
      Raise EW3Dataset.CreateFmt
      ('Failed to load dataset, system threw exception: %s',[e.message]);
    end;

    (* Verify header *)
    if mHead.dhMagic=$CAFE then
    Begin
      (* Load DEFS if any *)
      if mHead.dhFieldDefs.Length>0 then
      FDefs.LoadFromString(DecodeURI(mHead.dhFieldDefs));

      (* Any actual rows? ok, try to load them *)
      if mHead.dhCount>0 then
      Begin
        try
          asm
            (@self).FCache = JSON.parse((@mHead).dhData);
          end;
        except
          on e: exception do
          Raise EW3Dataset.CreateFmt
          ('Failed to load dataset, system threw exception: %s',[e.message]);
        end;
      end;

    end else
    Raise EW3Dataset.Create('Failed to load dataset, invalid header signature error');

  end else
  Raise EW3Dataset.Create('Failed to load dataset, string was empty error');

end;

Procedure TW3CustomDataset.CreateDataset;

  Procedure SetupFields;
  var
    x:  Integer;
  begin
    for x:=0 to FDefs.Count-1 do
    FFields.add(FDefs[x].Name,FDefs[x].Datatype);
  end;

Begin
  if not FActive then
  Begin

    if FDefs.Count>0 then
    Begin
      (* Clear any fields if the user has added some.
         All fields are created from filed-defs *)
      FFields.Clear;

      DoBeforeDatasetCreated;
      try
        FActive:=True;
        setState(dsIdle);
        setPosition(-1);

        (* Import table schema to field construct *)
        SetupFields;

        (* Lock fields & defs, read/write access to defined
           structure only. No alteration while the dataset
           is "live" *)
        (FFields as IDatasetFieldsAccess).setReadOnly(true);
        (FDefs as IFieldDefAccess).setReadOnly(true);

      finally
        DoAfterDatasetCreated;
      end;

    end else
    Raise EW3Dataset.Create('No field definitions for dataset error');
  end;
end;

Procedure TW3CustomDataset.Close;
begin
  if FActive then
  Begin
    DoBeforeDatasetClosed;
    try
      try
        FFields.Clear;
        FCache.Clear;
      finally
        FActive:=False;
        FState:=dsIdle;
        FDsIndex:=-1;
        (FFields as IDatasetFieldsAccess).resetValues;
        (FFields as IDatasetFieldsAccess).setReadOnly(False);
        (FDefs as IFieldDefAccess).setReadOnly(False);
      end;
    finally
      DoAfterDatasetClosed;
    end;
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

procedure TW3CustomDataset.setState(const aState:TW3DatasetState);
Begin
  FState:=aState;
  if assigned(FOnState) then
  FOnState(self,aState);
end;

Procedure TW3CustomDataset.getPacketToFields;
Begin
  if (FdsIndex>=0) and (FdsIndex<FCache.Length) then
  FFields.DataImport(FCache[FdsIndex]);
end;

Procedure TW3CustomDataset.setPacketFromFields;
Begin
  if (FdsIndex>=0) and (FdsIndex<FCache.Length) then
  FCache[FDsIndex]:=FFields.DataExport;
end;

Procedure TW3CustomDataset.setPosition(const aNewPosition:Integer);
var
  mOld: Integer;
Begin
  if aNewPosition<>FdsIndex then
  begin
    mOld:=FdsIndex;
    FDsIndex:=aNewPosition;

    if not (FState=dsInsert) then
    Begin
      if (FdsIndex>=0)
      and (FdsIndex<getRecCount) then
      getPacketToFields;
    end;

    if assigned(FOnPos) then
    FOnPos(self,mOld,aNewPosition);
  end;
end;

procedure TW3CustomDataset.setActive(const aValue:Boolean);
Begin
  if aValue<>FActive then
  begin
    Case aValue of
    true:   CreateDataset;
    false:  Close;
    end;
  end;
end;

Procedure TW3CustomDataset.UpdateCalculatedFields;
var
  x:  Integer;
Begin
  for x:=0 to FFields.Count-1 do
  if FFields.Items[x].calculated then
  FFields.items[x].Calculate;
end;

Procedure TW3CustomDataset.Append;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    Begin
      //Actual items + 1
      self.setPosition(FCache.Count);
      //Last;
      setState(dsInsert);
      (FFields as IDatasetFieldsAccess).resetValues;
      UpdateCalculatedFields;
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

procedure TW3CustomDataset.Insert;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    Begin
      if (RecNo<0) then
      setPosition(FCache.Count);
      setState(dsInsert);
      (FFields as IDatasetFieldsAccess).resetValues;
      UpdateCalculatedFields;
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

procedure TW3CustomDataset.Edit;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    Begin
      if FCache.Length>0 then
      Begin
        if  (FdsIndex=-1) then
        self.setPosition(0) else

        if (FdsIndex=FCache.length) then
        setPosition(FCache.length-1);

        setState(dsEdit);
        UpdateCalculatedFields;
      end;
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Delete;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    begin
      if Count>0 then
      Begin
        if (FdsIndex>=0) and (FdsIndex<FCache.count) then
        Begin

          (* Signal satan that his work is done *)
          if assigned(FOnDelete) then
          FOnDelete(self,FdsIndex);

          (* Delete record *)
          FCache.Delete(FdsIndex,1);

          (* Misaligned recno? Backtrack. This will fall back to -1,
             which is BOF, when the last record is deleted *)
          if FdsIndex>=FCache.Count then
          FdsIndex:=FCache.Count-1;

        end else
        raise EW3Dataset.CreateFmt('Delete failed, misaligned RecNo [%s]',[FdsIndex]);
      end else
      Raise EW3Dataset.Create('Delete failed, dataset is empty error');
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Cancel;
Begin
  if FActive then
  begin
    if (FState=dsInsert)
    or (FState=dsEdit) then
    begin
      setState(dsIdle);
      (FFields as IDatasetFieldsAccess).resetValues;
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Post;
var
  mDummy: TW3DatasetPacket;
Begin
  if FActive then
  Begin

    Case FState of
    dsInsert:
      Begin
        (* Grow the internal cache *)
        if FdsIndex=getRecCount then
        Begin
          (* Insert at end of Dataset (a.k.a "Append") *)
          FCache.Add(mDummy);
          FdsIndex:=FCache.Length-1;
        end else
        (* insert "directly" at position *)
        FCache.Insert(FDsIndex,mDummy);

        (* Write data to record pointer *)
        setPacketFromFields;

        (* position record PTR on last record *)
        //setPosition(FCache.Count-1);

        (* set state to idle *)
        setState(dsIdle);

      end;
    dsEdit:
      begin
        setPacketFromFields;
        setState(dsIdle);
      end;
    else
      raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
    end;

  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.First;
var
  mpos: Integer;
Begin
  if FActive then
  begin
    if FState=dsIdle then
    Begin
      if getRecCount>0 then
      mPos:=0 else
      mpos:=-1;
      setPosition(mPos);
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Last;
var
  mpos: Integer;
Begin
  if FActive then
  begin
    if FState=dsIdle then
    Begin
      if getRecCount>0 then
      mPos:=getRecCount-1 else
      mpos:=-1;
      setPosition(mPos);
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Next;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    setPosition(TInteger.EnsureRange(FDsIndex+1,-1,getRecCount)) else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Back;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    setPosition(TInteger.EnsureRange(FDsIndex+1,-1,getRecCount)) else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.MoveTo(const aRecNo:Integer);
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    setPosition(TInteger.EnsureRange(aRecNo,0,getRecCount-1)) else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.DoBeforeDatasetCreated;
Begin
end;

Procedure TW3CustomDataset.DoAfterDatasetCreated;
Begin
  if assigned(FOnCreated) then
  Begin
    if not FDestroying then
    FOnCreated(self);
  end;
end;

Procedure TW3CustomDataset.DoBeforeDatasetClosed;
Begin
end;

Procedure TW3CustomDataset.DoAfterDatasetClosed;
Begin
  if assigned(FOnClosed) then
  Begin
    if not FDestroying then
    FOnClosed(self);
  end;
end;

function TW3CustomDataset.getRecCount:Integer;
Begin
  result:=FCache.Count;
end;

function TW3CustomDataset.getRecNo:Integer;
Begin
  if FActive then
  result:=FDsIndex else
  result:=-1;
end;

function TW3CustomDataset.getEOF:Boolean;
Begin
  if FActive then
  result:=FDsIndex>=getRecCount else
  result:=True;
end;

function TW3CustomDataset.getBOF:Boolean;
Begin
  if FActive then
  result:=FDsIndex<=0 else
  result:=True;
end;

//#############################################################################
// TW3DatasetFields
//#############################################################################

Destructor TW3DatasetFields.Destroy;
Begin
  Clear;
  inherited;
end;

Procedure TW3DatasetFields.setReadOnly(const aValue:Boolean);
var
  x:  Integer;
begin
  if FFields.Length>0 then
  Begin
    for x:=FFields.Low to FFields.High do
    FFields[x].setReadOnly(aValue);
  end;
end;

Procedure TW3DatasetFields.ResetValues;
var
  mItem:  TW3CustomDatasetField;
Begin
  if FFields.Length>0 then
  Begin
    for mItem in FFields do
    mItem.setValue(null);
  end;
end;

Procedure TW3DatasetFields.Clear;
var
  x:  Integer;
Begin
  if FFields.Count>0 then
  Begin
    try
      for x:=FFields.Low to FFields.High do
      FFields[x].free;
    finally
      FFields.Clear;
    end;
  end;
end;

Procedure TW3DatasetFields.DeleteByName(aName:String);
var
  mIndex: Integer;
Begin
  mIndex:=IndexOf(aName);
  if mIndex>=0 then
  Begin
    FFields[mIndex].free;
    FFields.Delete(mIndex,1);
  end;
end;

Procedure TW3DatasetFields.Delete(const aIndex:Integer);
Begin
  if (aIndex>=0) and (aIndex<FFields.Count) then
  Begin
    FFields[aIndex].free;
    FFields.Delete(aIndex,1);
  end;
end;

function TW3DatasetFields.Add(aName:String;
        const aKind:TW3DatasetFieldType):TW3CustomDatasetField;
Begin
  result:=NIL;
  aName:=lowercase(trim(aName));
  if aName.Length>0 then
  Begin
    if IndexOf(aName)=-1 then
    Begin
      case aKind of
      ftBoolean:  result:=TW3BooleanField.Create(self);
      ftInteger:  result:=TW3IntegerField.Create(self);
      ftFloat:    result:=TW3FloatField.Create(self);
      ftString:   result:=TW3StringField.Create(self);
      ftDateTime: result:=TW3DateTimeField.Create(self);
      ftAutoInc:  result:=TW3AutoIncField.Create(self);
      ftGUID:     result:=TW3GUIDField.Create(self);
      else        result:=NIL;
      end;

      if result<>NIL then
      Begin
        result.Name:=aName;
        FFields.add(result);
      end else
      Raise EW3DatasetField.Create(CNT_DATASET_FIELD_UNKNOWN);

    end;
  end;
end;

function TW3DatasetFields.DataExport:TW3DatasetPacket;
var
  x:  Integer;
  mField:TW3CustomDatasetField;
Begin
  result:=TVariant.CreateObject;
  if FFields.count>0 then
  Begin
    for mField in FFields do
    result[mField.Name]:=mField.getValue;
  end;
end;

Procedure TW3DatasetFields.DataImport(const aValue:TW3DatasetPacket);
var
  mField:TW3CustomDatasetField;
Begin
  if FFields.count>0 then
  Begin
    for mField in FFields do
    mField.setvalue(aValue[mField.Name]);
  end;
end;

function TW3DatasetFields.IndexOf(aName:String):Integer;
var
  x:  Integer;
Begin
  result:=-1;
  aName:=lowercase(trim(aName));
  if aName.Length>0 then
  Begin
    for x:=FFields.Low to FFIelds.High do
    Begin
      If FFIelds[x].Name=aName then
      Begin
        result:=x;
        break;
      end;
    end;
  end;
end;

function TW3DatasetFields.FieldByName(aName:String):TW3CustomDatasetField;
var
  x:  Integer;
Begin
  result:=NIL;
  aName:=lowercase(trim(aName));
  if aName.Length>0 then
  Begin
    for x:=FFields.Low to FFields.High do
    Begin
      If FFields[x].Name=aName then
      Begin
        result:=FFields[x];
        break;
      end;
    end;
  end;
end;

//#############################################################################
// TW3GUIDField
//#############################################################################

function TW3GUIDField.getCalculated:Boolean;
Begin
  result:=true;
end;

Procedure TW3GUIDField.Calculate;
Begin
  inherited setValue(uppercase(TW3GUID.createGUID));
end;

//#############################################################################
// TW3AutoIncField
//#############################################################################

function TW3AutoIncField.getCalculated:Boolean;
Begin
  result:=true;
end;

Procedure TW3AutoIncField.Calculate;
Begin
  inc(FCalc);
  inherited setValue(FCalc);
end;

//#############################################################################
// TW3DateTimeField
//#############################################################################

function TW3DateTimeField.getValue:TDateTime;
Begin
  result:=inherited getValue;
end;

procedure TW3DateTimeField.setValue(const aValue:TDateTime);
Begin
  inherited setValue(aValue);
end;

//#############################################################################
// TW3CustomDatasetField
//#############################################################################

Constructor TW3CustomDatasetField.Create(const aParent:TW3DatasetFields);
Begin
  inherited Create;
  FParent:=aParent;
  FKind:=ftUnknown;
  FName:='Field' + IntToStr( w3_GetUniqueNumber );
end;

Procedure TW3CustomDatasetField.setAsDateTime(const aValue:TDateTime);
Begin
  FValue:=aValue;
end;

function TW3CustomDatasetField.getAsDateTime:TDateTime;
Begin
  result:=FValue;
end;

Procedure TW3CustomDatasetField.Calculate;
Begin
end;

function TW3CustomDatasetField.getCalculated:Boolean;
Begin
  result:=False;
end;

function TW3CustomDatasetField.getValue:Variant;
Begin
  result:=FValue;
end;

procedure TW3CustomDatasetField.setValue(const aValue:variant);
Begin
  FValue:=aValue;
end;

procedure TW3CustomDatasetField.setName(const aValue:String);
Begin
  if not FReadOnly then
  FName:=lowercase(trim(aValue)) else
  Raise EW3DatasetField.Create(CNT_DATASET_FIELD_READONLY);
end;

procedure TW3CustomDatasetField.setKind(const aKind:TW3DatasetFieldType);
Begin
  if not FReadOnly then
  FKind:=aKind else
  Raise EW3DatasetField.Create(CNT_DATASET_FIELD_READONLY);
end;

Procedure TW3CustomDatasetField.setReadOnly(const aValue:Boolean);
Begin
  FReadOnly:=aValue;
end;

end.

Dataset for Smart Mobile Studio

July 8, 2014 Leave a comment

I’ve only had a couple of hours to work on this, but here is the first incarnation of TDataset for Smart Mobile Studio. It will need some work (and a lot of testing) before it’s released into the wild – so feel free to play around with it, but dont use it in production level projects just yet.

Notice the heavy use of Property Expressions, a unique feature of Smart Pascal. Especially in the field classes, where property expressions remove the need for at least 2 getter/setter methods. Fast, elegant and very pleasant to write.

Using the dataset

Fairly straight forward stuff. Here is how you create a dataset:

  FDataset.Fields.Add('id',ftInteger);
  FDataset.Fields.add('name',ftString);
  FDataset.CreateDataset;

And you insert data in much the same way as under Delphi:

  for x:=0 to 50 do
  Begin
    FDataset.Insert;
    FDataset.fields.fieldByName('id').asInteger:=x+1;
    FDataset.Fields.FieldByName('name').asString:='this is String #' + x.ToString;
    FDataset.Post;
  end;

And you also edit stuff in much the same way as you would in Delphi or Lazarus:

  FDataset.first;
  FDataset.edit;
  FDataset.Fields.FieldByName('name').asString:='We altered this one!';
  FDataset.post;

And last but not least, navigation should be en-par with what you are used to:

  FDataset.first;
  repeat
    writeln(FDataset.Fields.FieldByName('name').asString);
    FDataset.next;
  until FDataset.EOF;

Note: The data is stored in the FCache array, to export the data, just use JSON to export it – and voila, you can now use datasets to deal with in-app orders, posts, news or whatever. When updating a real database, send the whole dataset as a packet — easy as apple pie 🙂

JSON representation of the data

JSON representation of the data

A simple “Save” routine would look something like this:

Function TW3CustomDataset.saveToString:String;
Begin
  asm
    @result = JSON.stringify((@self).FCache);
  end;
end;

 

Well, enjoy!

 

unit w3dataset;

//Copyright Jon Lennart Aasenden, All right reserved.

interface

uses
  W3System;

  type

  TW3CustomDatasetField = Class;
  TW3BooleanField       = Class;
  TW3IntegerField       = Class;
  TW3FloatField         = Class;
  TW3StringField        = Class;
  TW3DatasetFields      = Class;
  TW3CustomDataset      = Class;

  EW3DatasetField       = Class(EW3Exception);
  EW3Dataset            = Class(EW3Exception);

  TW3DatasetPacket      = Variant;

  TW3DatasetFieldType =
    (
        ftUnknown,
        ftBoolean,
        ftInteger,
        ftFloat,
        ftString,
        ftDateTime
    );

  IDatasetFieldsAccess  = Interface
    Procedure  ResetValues;
    Procedure  setReadOnly(const aValue:Boolean);
    Function   ExportDefs:String;
    Procedure  ImportDefs(const aDefs:String);
  end;

  TW3CustomDatasetField = Class(TObject)
  private
    FName:      String;
    FKind:      TW3DatasetFieldType;
    FReadOnly:  Boolean;
    FValue:     Variant;
    FParent:    TW3DatasetFields;
    Procedure   setAsDateTime(const aValue:TDateTime);
    function    getAsDateTime:TDateTime;
  protected
    function    getValue:Variant;virtual;
    procedure   setValue(const aValue:variant);virtual;
    procedure   setName (const aValue:String);
    procedure   setKind (const aKind:TW3DatasetFieldType);
    Procedure   setReadOnly(const aValue:Boolean);
  public
    Property    Parent:TW3DatasetFields read FParent;
    Property    Kind:TW3DatasetFieldType read FKind;
    Property    Name:String read FName write setName;
    Property    Value:Variant read FValue write FValue;

    Property    AsString:String
                read (TVariant.AsString(FValue))
                write (FValue:=Value);

    Property    AsInteger:Integer
                read (TVariant.asInteger(FValue))
                write (FValue:=Value);

    Property    AsBoolean:Boolean
                read (TVariant.AsBool(FValue))
                write (FValue:=Value);

    Property    AsFloat:Float
                read (TVariant.asFloat(FValue))
                write (FValue:=Value);

    Property    AsDateTime:TDateTime
                read  getAsDateTime
                write setAsDateTime;

    Constructor Create(const aParent:TW3DatasetFields);virtual;
  End;

  TW3BooleanField = Class(TW3CustomDatasetField)
  public
    Property  Value:Boolean
              read (TVariant.AsBool(Inherited getValue))
              write (inherited setValue(Value));
  End;

  TW3IntegerField = Class(TW3CustomDatasetField)
  public
    Property  Value:Integer
              read (TVariant.AsInteger(Inherited getValue))
              write (inherited setValue(Value));
  end;

  TW3FloatField = Class(TW3CustomDatasetField)
  public
    Property  Value:Float
              read (TVariant.AsFloat(Inherited getValue))
              write (inherited setValue(Value));
  end;

  TW3StringField = Class(TW3CustomDatasetField)
  public
    Property  Value:String
              read (TVariant.AsString(Inherited getValue))
              write (inherited setValue(Value));
  end;

  TW3DateTimeField = Class(TW3CustomDatasetField)
  protected
    function  getValue:TDateTime;reintroduce;
    procedure setValue(const aValue:TDateTime);reintroduce;
  public
    Property  Value:TDateTime read getValue write setValue;
  End;

  TW3DatasetFields = Class(TObject,IDatasetFieldsAccess)
  private
    FFields:    Array of TW3CustomDatasetField;
  protected
    (* IMPLEMENTS:: IDatasetFieldsAccess *)
    Procedure   ResetValues;
    Procedure   setReadOnly(const aValue:Boolean);
    Function    ExportDefs:String;virtual;
    Procedure   ImportDefs(const aDefs:String);virtual;
  public
    Property    Count:Integer read (FFields.count);
    Property    Items[index:Integer]:TW3CustomDatasetField read (FFields[index]);
    function    DataExport:TW3DatasetPacket;virtual;
    Procedure   DataImport(const aValue:TW3DatasetPacket);virtual;
    function    IndexOf(aName:String):Integer;
    function    Add(aName:String;
                const aKind:TW3DatasetFieldType):TW3CustomDatasetField;
    Procedure   DeleteByName(aName:String);
    Procedure   Delete(const aIndex:Integer);
    Procedure   Clear;
    function    FieldByName(aName:String):TW3CustomDatasetField;
    Destructor  Destroy;Override;
  End;

  TW3DatasetState =(dsIdle,dsInsert,dsEdit);

  TDatasetStateChangeEvent = Procedure (sender:TObject;aState:TW3DatasetState);
  TDatasetPositionChangeEvent = procedure (sender:TObject;aOldPos,aNewPos:Integer);

  TW3CustomDataset = Class(TObject)
  private
    FFields:    TW3DatasetFields;
    FCache:     Array of TW3DatasetPacket;
    FState:     TW3DatasetState;
    FActive:    Boolean;
    FDestroying:Boolean;

    FOnCreated: TNotifyEvent;
    FOnClosed:  TNotifyEvent;
    FOnState:   TDatasetStateChangeEvent;
    FOnPos:     TDatasetPositionChangeEvent;

    FDsIndex:   Integer;

    procedure   setActive(const aValue:Boolean);
    Procedure   setPosition(const aNewPosition:Integer);
  protected
    Procedure   DoBeforeDatasetCreated;virtual;
    Procedure   DoAfterDatasetCreated;virtual;
    Procedure   DoBeforeDatasetClosed;virtual;
    Procedure   DoAfterDatasetClosed;virtual;

    function    getRecCount:Integer;virtual;
    function    getRecNo:Integer;virtual;
    function    getEOF:Boolean;virtual;
    function    getBOF:Boolean;virtual;

    procedure   getPacketToFields;virtual;
    Procedure   setPacketFromFields;virtual;

    procedure   setState(const aState:TW3DatasetState);
  public
    Property    Active:Boolean read FActive write setActive;
    Property    State:TW3DatasetState read FState;
    Property    Fields:TW3DatasetFields read FFields;
    Property    EOF:Boolean read getEOF;
    Property    BOF:Boolean read getBOF;
    Property    Count:Integer read getRecCount;
    Property    RecNo:Integer read getRecNo;

    procedure   Insert;
    Procedure   Delete;
    Procedure   Post;
    procedure   Edit;
    Procedure   Next;
    Procedure   Back;

    Procedure   First;
    Procedure   Last;

    Procedure   CreateDataset;
    Procedure   Close;

    Procedure   Cancel;

    Constructor Create;virtual;
    Destructor  Destroy;Override;
  published
    Property    OnDatasetCreated:TNotifyEvent read FOnCreated write FOnCreated;
    Property    OnDatasetClosed:TNotifyevent read FOnClosed write FOnClosed;
    Property    OnStateChanged:TDatasetStateChangeEvent
                read FOnState write FOnState;
    Property    OnPositionChanged:TDatasetPositionChangeEvent
                read FOnPos write FOnPos;
  end;

  TW3Dataset = class(TW3CustomDataset)
  End;

  TW3DataSource = Class(TObject)
  end;

implementation

resourcestring
CNT_DATASET_FIELD_READONLY  = 'Failed to alter value, field is read-only error';
CNT_DATASET_NOT_ACTIVE      = 'Operation failed, dataset is not active error';
CNT_DATASET_INVALID_STATE   = 'Invalid state for operation error';

//#############################################################################
// TW3CustomDataset
//#############################################################################

Constructor TW3CustomDataset.Create;
Begin
  inherited Create;
  FFields:=TW3DatasetFields.Create;
  FState:=dsIdle;
  FDestroying:=False;
  FDsIndex:=-1;
end;

Destructor  TW3CustomDataset.Destroy;
Begin
  FDestroying:=true;

  if FActive then
  Close;

  FFields.free;
  inherited;
end;

Procedure TW3CustomDataset.Close;
begin
  if FActive then
  Begin
    DoBeforeDatasetClosed;
    try
      try
        FFields.Clear;
        FCache.Clear;
      finally
        FActive:=False;
        FState:=dsIdle;
        FDsIndex:=-1;
        (FFields as IDatasetFieldsAccess).resetValues;
      end;
    finally
      DoAfterDatasetClosed;
    end;
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

procedure TW3CustomDataset.setState(const aState:TW3DatasetState);
Begin
  FState:=aState;
  if assigned(FOnState) then
  FOnState(self,aState);
end;

Procedure TW3CustomDataset.getPacketToFields;
Begin
  if (FdsIndex>=0) and (FdsIndex<FCache.Length) then
  FFields.DataImport(FCache[FdsIndex]);
end;

Procedure TW3CustomDataset.setPacketFromFields;
Begin
  if (FdsIndex>=0) and (FdsIndex<FCache.Length) then
  FCache[FDsIndex]:=FFields.DataExport;
end;

Procedure TW3CustomDataset.setPosition(const aNewPosition:Integer);
var
  mOld: Integer;
Begin
  if aNewPosition<>FdsIndex then
  begin
    mOld:=FdsIndex;
    FDsIndex:=aNewPosition;

    if (FdsIndex>=0)
    and (FdsIndex<FCache.Length) then
    self.getPacketToFields else
    //FFields.DataImport(FCache[FdsIndex]) else
    (FFields as IDatasetFieldsAccess).resetValues;

    if assigned(FOnPos) then
    FOnPos(self,mOld,aNewPosition);
  end;
end;

Procedure TW3CustomDataset.CreateDataset;
Begin
  if not FActive then
  Begin

    if FFields.Count>0 then
    Begin
      DoBeforeDatasetCreated;
      try
        FActive:=True;
        setState(dsIdle);
        setPosition(-1);
      finally
        DoAfterDatasetCreated;
      end;
    end else
    Raise EW3Dataset.Create('No fields defined for dataset error');
  end;
end;

procedure TW3CustomDataset.setActive(const aValue:Boolean);
Begin
  if aValue<>FActive then
  begin
    Case aValue of
    true:   CreateDataset;
    false:  Close;
    end;
  end;
end;

procedure TW3CustomDataset.Insert;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    Begin
      Last;
      setState(dsInsert);
      (FFields as IDatasetFieldsAccess).resetValues;
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Delete;
Begin
end;

Procedure TW3CustomDataset.Cancel;
Begin
  if FActive then
  begin
    if (FState=dsInsert)
    or (FState=dsEdit) then
    begin
      setState(dsIdle);
      (FFields as IDatasetFieldsAccess).resetValues;
    end;
  end;
end;

Procedure TW3CustomDataset.Post;
var
  mDummy: TW3DatasetPacket;
Begin
  if FActive then
  Begin
    Case FState of
    dsInsert:
      Begin
        (* Grow the internal cache *)
        FCache.Add(mDummy);

        FdsIndex:=FCache.Length-1;
        setPacketFromFields;

        (* position record PTR on last record *)
        setPosition(FFields.Count-1);

        (* set state to idle *)
        setState(dsIdle);

      end;
    dsEdit:
      begin
        setPacketFromFields;
        setState(dsIdle);
      end;
    else
      raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
    end;
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

procedure TW3CustomDataset.Edit;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    Begin
      if FCache.Length>0 then
      Begin
        if  (FdsIndex=-1) then
        self.setPosition(0) else

        if (FdsIndex=FCache.length) then
        setPosition(FCache.length-1);

        setState(dsEdit);

      end;
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.First;
var
  mpos: Integer;
Begin
  if FActive then
  begin
    if FState=dsIdle then
    Begin
      if FCache.Length>0 then
      mPos:=0 else
      mpos:=-1;
      setPosition(mPos);
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Last;
var
  mpos: Integer;
Begin
  if FActive then
  begin
    if FState=dsIdle then
    Begin
      if FCache.Length>0 then
      mPos:=FCache.Length-1 else
      mpos:=-1;
      setPosition(mPos);
    end else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Next;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    setPosition(TInteger.EnsureRange(FDsIndex+1,-1,FCache.Length)) else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.Back;
Begin
  if FActive then
  Begin
    if FState=dsIdle then
    setPosition(TInteger.EnsureRange(FDsIndex+1,-1,FCache.Length)) else
    raise EW3Dataset.Create(CNT_DATASET_INVALID_STATE);
  end else
  raise EW3Dataset.Create(CNT_DATASET_NOT_ACTIVE);
end;

Procedure TW3CustomDataset.DoBeforeDatasetCreated;
Begin
end;

Procedure TW3CustomDataset.DoAfterDatasetCreated;
Begin
  if assigned(FOnCreated) then
  Begin
    if not FDestroying then
    FOnCreated(self);
  end;
end;

Procedure TW3CustomDataset.DoBeforeDatasetClosed;
Begin
end;

Procedure TW3CustomDataset.DoAfterDatasetClosed;
Begin
  if assigned(FOnClosed) then
  Begin
    if not FDestroying then
    FOnClosed(self);
  end;
end;

function TW3CustomDataset.getRecCount:Integer;
Begin
  result:=FCache.Length;
end;

function TW3CustomDataset.getRecNo:Integer;
Begin
  if FActive then
  result:=FDsIndex else
  result:=-1;
end;

function TW3CustomDataset.getEOF:Boolean;
Begin
  if FActive then
  result:=FDsIndex>=FCache.Length else
  result:=True;
end;

function TW3CustomDataset.getBOF:Boolean;
Begin
  if FActive then
  result:=FDsIndex<=0 else
  result:=True;
end;

//#############################################################################
// TW3DatasetFields
//#############################################################################

Destructor TW3DatasetFields.Destroy;
Begin
  Clear;
  inherited;
end;

Procedure TW3DatasetFields.setReadOnly(const aValue:Boolean);
var
  x:  Integer;
begin
  if FFields.Length>0 then
  Begin
    for x:=FFields.Low to FFields.High do
    FFields[x].setReadOnly(aValue);
  end;
end;

Function TW3DatasetFields.ExportDefs:String;
Begin
end;

Procedure TW3DatasetFields.ImportDefs(const aDefs:String);
Begin
end;

Procedure TW3DatasetFields.ResetValues;
var
  mItem:  TW3CustomDatasetField;
Begin
  if FFields.Length>0 then
  Begin
    for mItem in FFields do
    mItem.setValue(null);
  end;
end;

Procedure TW3DatasetFields.Clear;
var
  x:  Integer;
Begin
  if FFields.Count>0 then
  Begin
    try
      for x:=FFields.Low to FFields.High do
      FFields[x].free;
    finally
      FFields.Clear;
    end;
  end;
end;

Procedure TW3DatasetFields.DeleteByName(aName:String);
var
  mIndex: Integer;
Begin
  mIndex:=IndexOf(aName);
  if mIndex>=0 then
  Begin
    FFields[mIndex].free;
    FFields.Delete(mIndex,1);
  end;
end;

Procedure TW3DatasetFields.Delete(const aIndex:Integer);
Begin
  if (aIndex>=0) and (aIndex<FFields.Count) then
  Begin
    FFields[aIndex].free;
    FFields.Delete(aIndex,1);
  end;
end;

function TW3DatasetFields.Add(aName:String;
        const aKind:TW3DatasetFieldType):TW3CustomDatasetField;
Begin
  result:=NIL;
  aName:=lowercase(trim(aName));
  if aName.Length>0 then
  Begin
    if IndexOf(aName)=-1 then
    Begin
      case aKind of
      ftBoolean:  result:=TW3BooleanField.Create(self);
      ftInteger:  result:=TW3IntegerField.Create(self);
      ftFloat:    result:=TW3FloatField.Create(self);
      ftString:   result:=TW3StringField.Create(self);
      ftDateTime: result:=TW3DateTimeField.Create(self);
      else        result:=NIL;
      end;

      if result<>NIL then
      Begin
        result.Name:=aName;
        FFields.add(result);
      end;

    end;
  end;
end;

function TW3DatasetFields.DataExport:TW3DatasetPacket;
var
  x:  Integer;
  mField:TW3CustomDatasetField;
Begin
  result:=TVariant.CreateObject;
  if FFields.count>0 then
  Begin
    for mField in FFields do
    result[mField.Name]:=mField.getValue;
  end;
end;

Procedure TW3DatasetFields.DataImport(const aValue:TW3DatasetPacket);
var
  mField:TW3CustomDatasetField;
Begin
  if FFields.count>0 then
  Begin
    for mField in FFields do
    mField.setvalue(aValue[mField.Name]);
  end;
end;

function TW3DatasetFields.IndexOf(aName:String):Integer;
var
  x:  Integer;
Begin
  result:=-1;
  aName:=lowercase(trim(aName));
  if aName.Length>0 then
  Begin
    for x:=FFields.Low to FFIelds.High do
    Begin
      If FFIelds[x].Name=aName then
      Begin
        result:=x;
        break;
      end;
    end;
  end;
end;

function TW3DatasetFields.FieldByName(aName:String):TW3CustomDatasetField;
var
  x:  Integer;
Begin
  result:=NIL;
  aName:=lowercase(trim(aName));
  if aName.Length>0 then
  Begin
    for x:=FFields.Low to FFields.High do
    Begin
      If FFields[x].Name=aName then
      Begin
        result:=FFields[x];
        break;
      end;
    end;
  end;
end;

//#############################################################################
// TW3DateTimeField
//#############################################################################

function TW3DateTimeField.getValue:TDateTime;
Begin
  result:=inherited getValue;
end;

procedure TW3DateTimeField.setValue(const aValue:TDateTime);
Begin
  inherited setValue(aValue);
end;

//#############################################################################
// TW3CustomDatasetField
//#############################################################################

Constructor TW3CustomDatasetField.Create(const aParent:TW3DatasetFields);
Begin
  inherited Create;
  FParent:=aParent;
  FKind:=ftUnknown;
  FName:='Field' + IntToStr( w3_GetUniqueNumber );
end;

Procedure TW3CustomDatasetField.setAsDateTime(const aValue:TDateTime);
Begin
  FValue:=aValue;
end;

function TW3CustomDatasetField.getAsDateTime:TDateTime;
Begin
  result:=FValue;
end;

function TW3CustomDatasetField.getValue:Variant;
Begin
  result:=FValue;
end;

procedure TW3CustomDatasetField.setValue(const aValue:variant);
Begin
  FValue:=aValue;
end;

procedure TW3CustomDatasetField.setName(const aValue:String);
Begin
  if not FReadOnly then
  FName:=lowercase(trim(aValue)) else
  Raise EW3DatasetField.Create(CNT_DATASET_FIELD_READONLY);
end;

procedure TW3CustomDatasetField.setKind(const aKind:TW3DatasetFieldType);
Begin
  if not FReadOnly then
  FKind:=aKind else
  Raise EW3DatasetField.Create(CNT_DATASET_FIELD_READONLY);
end;

Procedure TW3CustomDatasetField.setReadOnly(const aValue:Boolean);
Begin
  FReadOnly:=aValue;
end;

end.

Smart Pascal dataset

July 4, 2014 Leave a comment

As I have mentioned earlier in my posts, Smart Pascal have no default database framework. Smart Mobile Studio (which is the #1 compiler for Smart Pascal) have wrappers for all the various browser database engines, so using them is greatly simplified for Smart programmers — but there are no such things as datasets, field objects and any of the standard stuff we are used to under Delphi or Freepascal.

Well, today I had enough and started writing the basics!

The cool part is that dealing with data is, believe it or not, much easier in Smart Pascal than under native Delphi. For instance, a row of data (a data packet) is stored in JSON format, and JavaScript’s live prototype nature makes it extremely elegant to read, write and update a large collection of packets. Being able to read, write and update data from your server or the cloud is, to my surprise.. easy as apple pie!

Screendump of my vmware for smart pascal

Screendump of my vmware for smart pascal

For instance, what is a record? Answer: It is a collection of persistent, named field objects. Under JavaScript that can be easily represented as a JSON object – or “array of named variants” in Object Pascal terms. So you get the naming of data free of charge, because that’s how JavaScript works. And you also get the serialization of data free of charge, because that’s how JSON works. So roughly 25% of the work required by “native” languages in order to represent a dataset – is already done. Pretty cool huh? 🙂

And yes, this code will work under any Smart Pascal compliant system. DWScript, Smart Mobile Studio and Quartex Pascal. But it will ship first and foremost with Smart Mobile Studio.

And now for something completely different

Bored? Check out the TV series “Silicon Valley” from HBO, I laughed so hard I nearly broke a rib. The series if a complete rip-off from the british “IT crowd” (even the music), but it reminded me so much of Borland vs. Microsoft that I nearly fell out of my couch 😀

Silicon Valley

Phillipe Kahn on drugs 🙂

Learning Smart Pascal, a primer

July 2, 2014 7 comments

One of the questions I get the most when it comes to Smart Pascal and Smart Mobile Studio, is “how do I learn the language?”. The short answer to this question is, if you know Delphi or Freepascal, that you already know it.

Linguistically Smart Pascal is more or less identical to ordinary Object Pascal (Delphi and FreePascal). So as a Delphi programmer you can jump straight into it and start coding – as long as you remember the following:

  • JavaScript has no pointers
  • You don’t need to postfix your event declarations with “of object”
  • Interfaces are supported by TObject
  • Threads are written in separate units (web workers)
  • Our run-time library is VJL, Visual JavaScript Library
  • Sets are not supported, but easily emulated
  • Generics is not supported

That really is everything you need to know, taking for granted naturally -that you already know Delphi or Freepascal and are not afraid to write code. I must admit that I did not expect people to be so dependent on the visual designer as they clearly are – but linguistically Smart Pascal is just “Delphi with a twist”. I don’t mean anything negative about that, I was just surprised that so many programmers are like fish out of water when you remove the visual aids. I rarely use the visual designer, except for form layout. Everything else is written in code, class by class, hierarchy by hierarchy.

Once you get used to compiling to JavaScript rather than machine code, you can begin to use the “smart” part of our pascal dialect, namely lambdas, array operations, partial classes, anonymous classes, class variables and much, much more (please see wikipedia article for detailed descriptions of these advances).

The single most powerful difference is probably array operations. Under Delphi you are used to TList, TObjectList and lately, generics (not to mention stacks and queues). Under Smart Pascal arrays are more evolved and replaces these concepts. All arrays, regardless of type, support a wide range of operations. Once you understand how flexible and fast this is – it will change how you write your code profoundly. You might even end up missing these features in Delphi and implement a helper for it (like I have done).

Procedure TForm1.playValues;
var
  mItems: Array of Variant;
begin
  mItems.add(1200);
  mItems.add('this is a string');
  mItems.add(12.5);
  mItems.push(19.22);
  writeln(mItems.pop);
end;

Check out Array Operations on wikipedia for a list of methods supported by all arrays.

Where to begin

Just like under C# you are expected to write code

Just like under C# you are expected to write code

The reason we did not supply documentation is twofold: when I started the project I designed it to be a Delphi only tool, meaning that you were supposed to simply write ordinary Delphi (hence the link to Delphi Basics in the application). SMS was initially a utility by Delphi programmers for Delphi programmers. Sadly, Embarcadero has made it perfectly clear that they regard us as a threat rather than friends so we had no choice but to abandon VCL emulation and focus on our own, proprietary run-time library.

This attitude still baffles me because it represents a great loss of income and opportunity for Delphi programmers worldwide. Object Pascal as a language is the bread and butter of the Delphi community, and the more people use our language – the greater the interest and investment in Delphi there will be.

But you can probably imagine what our compiler could do if deployed with Delphi. The live HTML5 reporting capabilities alone would revolutionize how we present numbers and interact with online services – and being able to compile to HTML5 would really set Delphi apart from other development platforms. Just imagine Intraweb powered by our compiler technology? Being able to use the same language both natively and in the browser – compiled into lightning fast JavaScript on the spot. That would be a very powerful technology indeed. But sadly this is not something Embarcadero wanted. I have given up on Embarcadero long ago and wont waste any more time regarding this topic.

The second reason is, obviously, JavaScript itself. The reality of the browser is so different and alien compared to the Microsoft Windows API, that a full VCL emulation layer would be fatal to execution time. So while it would be nice to be 100% Delphi compatible both linguistically and architecturally, performance wise it would be suicide. And since Embarcadero made it so clear that our hard work and service to the Delphi community is unwanted (due to the fact that it makes HTML5 builder look like a raving imbecile) it made more sense writing our own from scratch. Smart Mobile Studio is what Delphi for PHP should have been.

A brand new run-time library for a brand new platform, boldly taking Object Pascal where no compiler has gone before.

I am probably the first human being to compile an Object Pascal program and executing it on my Smart TV. Never before has Object Pascal run on a Phillips TV with an embedded browser — and never before have Object Pascal been used to control micro-processors. It was not Embarcadero that delivered this — it was us.

Real code, real work

Get a good book on JavaScript

Get a good book on JavaScript

If you go out and buy Mono C# for mobile development you are expected, just like under Smart Pascal, to study the RTL source-code and familiarize yourself with the units. We have reflected some of the VCL architecture (which really is universal, since that’s how most run-time libraries work), such as TW3CustomControl and TW3Component which are as identical to their Delphi equivalents as JavaScript allows.

You are also expected to tailor custom-controls yourself and adapt the standard controls and classes to your solution. This is how real software is made in all other languages, including Objective C, C# and C++. And while Delphi still retains it’s Visual Basic like, RAD, event driven development interface –Object Pascal is just as capable as C# and C++ for serious development. But the visual design tools quickly go out the window when you want to write rock-solid applications, be they for the mobile platform or not.

So in short: If you know Delphi and are not afraid of writing your own classes, which I really regard as a primer for calling yourself a programmer, then Smart Pascal will be mastered in a couple of days. You can also cut the learning curve even more by getting a book on Smart Pascal.

If you are new to Object Pascal then a quick visit to Delphi Basics will help you learn our language quickly and effectively.

And just like Delphi – we provide a “Demos” folder when you install Smart. This really is the #1 place to find example code. Everything from touch management, scrolling, hardware GPU powered sprites and more is covered here. A wast tome of knowledge ready to be played with and learned from.

If I was to learn Smart Pascal today, I would get the following:

It is not “vital” that you learn JavaScript, but learning about the browser objects, JavaScript libraries and elements is important. But it’s not complex and any middle-level programmer will master the browser quickly. Once you have played around with JavaScript a couple of days, reading about what JavaScript can do and how to do it — then pick up the Smart Book and enjoy.

JavaScript has no classes, no custom types — but Smart Pascal does. It will save you so much time and solve so many common JavaScript problems straight out of the box.

Knowing that you can use Smart Pascal to control hardware, like the new JS based controllers which is making their entry into the embedded marked, hard-core HTML5 mobile applications, games, multimedia and serious business applications — it’s more than worth the couple of days it takes learning the ropes.

Enjoy!