Home > Delphi, OP4JS > Dataset for Smart Mobile Studio

Dataset for Smart Mobile Studio

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.

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: