segunda-feira, 8 de agosto de 2016

Smart Mobile Studio - Hybrid UI

I can say that is possible to write complex applications with Smart Mobile Studio, you just have to know its limitations. Limitations, you will also face with other tools like TypeScript. For instance, depending on what you plan to do, you can use only the SMS IDE, and use pure Object Pascal, of course, you have to do everything by hand, at least you have control over everything.

Sooner or later, you will stumble working on the user interface. This is often the biggest challenge facing any mobile development project. SMS offer a solution to the user interface - the SCL framework.

SCL approach

As you know, the SMS UI part is handled by the SCL (SmartCL Framework). It has a basic component library, including source code. Simply dragging and dropping visual components on the visual designer, and changing their appearance and behaviors as desired with the Object Inspector, like the old Delphi 7.

You even can create new components based upon existing components and add then to the component library. These components responds to events, typically user actions, and it invokes changes on the view.

Hand-written code

One thing that I tend to disagree is using the RAD approach to build, develop and manage the user interface. I sincerely do not like of this methodology, which I believe is difficult to work on real projects (with a lot of views).

I'll prefer hand-written code (write HTML/CSS code manually), I consider to be more readable, more maintainable and believe to offer more customization and abstractions. CSS is all about presentation, and it takes a good amount of tweaking to get things looking right across different browsers.
Using this approach, the visual designer is not required.

Hybrid Approach

SCL (SmartCL framework) has its strengths, removing completely SmartCL* components from your smart project can severely restrict the use of the some nice features.

The short of it is: You’re not going to be able to build an entire application in SMS Visual Designer (at least not yet anyway), but it is an nice tool for quickly creating UI and prototype apps.

What is a hybrid? The proposal is using hybrid designer-developer combo.  Part developer (views created using the visual designer / SCL), part designer (views polished by true designers).

Once you understand the limitations of the SmartCL*, what is and what isn't possible currently in your design. Some views, can be created by using the visual designer. Another part —  a talented person, a HTML5/CSS designer. I’m convinced that hand-coding is an essential to create beautiful views. Hand-coding also lets you create smaller files than a software package. It's faster to create finished, tidy web page templates by hand-coding than it is to use a WYSIWYG editor, for instance.

Using this methodology, it is possible to interfacing some mostly used frameworks like ionics, F7, Material Design UI with the SCL visual designer. Using this idea will give us plenty of benefits.  

In the following experiment, I'm going to use the hybrid methodology. The intro, blog, gallery, tabs, login, social, video and contact views should be created by using hand coding approach. Only two views (Form1 and Form2) were created using the SMS visual designer.

My 2 cents

I, myself, know how to code pascal to some extent. But I find myself coding less and less, ultimately. Since designing a more complex component task is time consuming. I still feel obliged to learn and pick up new coding skills every once in a while, which is nice – I always got overloaded styling a component.

I'm frustrating with the UI processes, instead of focusing on the smart strengths - on the logic, the smart compiler to write and debug business logic in its powerful language. Using the current approach we need to be a rockstart designer-developer to design beautiful designers.

quinta-feira, 4 de agosto de 2016

Working with small JSON data sets in memory using Smart Mobile Studio

TW3DataSet

TW3Dataset is simply a small in-memory datastore that allowed you to save small data. You can save data to a normal string or stream. It also can be used as an intermediate format, you can both push data to a server (small number of records) as well as retrieve data from a server. 
If you want to persist some JSON information in the browser. Depending on user interaction with the application, you want to store 5-6 different JSON object into memory, the TW3DataSet is an option. This is great to create prototyping applications.
Note: storing large data in memory has a couple of disadvantages:
  • non-scalable — when you decide to use more processes, each process will need to make same api request;
  • fragile — if your process crashes you will lose the data.
Also working with large amount of data can block process for longer time than you would like.Solution: I'll use external storage! It can be MongoDB or RDBMS; - update data in separate process, triggered with cron; - don't drop the whole database: there is a chance that someone will make a request right after that (if your storage doesn't support transactions, of course), update records. Working with large JSON datasets can be a pain, particularly when they are too large to fit into memory.  

Example JSON

The following is an example of the JSON for the Products table. The JSON returned by the Delphi web server have this format: 

[{
 "productid" : "9V-BATTERY-12PK",
 "description" : "12-pack of 9-volt batteries",
 "listprice" : 20,
 "shipping" : 2
 }, {
 "productid" : "9V-BATTERY-4PK",
 "description" : "4-pack of 9-volt batteries",
 "listprice" : 4.5,
 "shipping" : 1.5
 }
]

We can fill a combobox using TW3Dataset, like in this example:

Define/Create the dataset 

Take a closer look at the above JSON format returned by our server (column and row data).  Before we create a dataset, we have to define what the table looks like, we have to define the field-definition property.
Ensure to add the System.Dataset unit;
  ProductsDS := TDataset.Create;
  
  ProductsDS.FieldDefs.Add('productid',ftString);
  ProductsDS.fieldDefs.Add('description',ftString);
  ProductsDS.fieldDefs.Add('listprice',ftFloat);
  ProductsDS.fieldDefs.add('shipping',ftFloat);
  ProductsDS.CreateDataset;
 DataSet Columns: When we define manually a dataset and the SaveToString method is called from the application.
The following JSON format is returned by our application:
{
 "dhMagic" : 51966,
 "dhCount" : 0,
 "dhFieldDefs" : {
  "ddMagic" : 3401235116,
  "ddDefs" : [{
    "fdName" : "productid",
    "fdDatatype" : 4
   }, {
    "fdName" : "description",
    "fdDatatype" : 4
   }, {
    "fdName" : "listprice",
    "fdDatatype" : 3
   }, {
    "fdName" : "shipping",
    "fdDatatype" : 3
   }
  ]
 },
 "dhData" : []
}
Field Types: Observe the following details the various column types and how they should be specified:
Field Type Code Description
ftUnknown 0 Unknown type - not specified collumn type
ftBoolean 1 Boolean
ftInteger 2 Integer
ftFloat 3 Float
ftString 4 String
ftDateTime 5 DateTime/Float
ftAutoInc 6 generated field
ftGUID 7 generated field

Adding records

This task is more or less identical to how you would do it under Delphi.
You have both append and insert operations. Let's use Append method for this example:

  procedure fillProductsDS;
  begin
    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := '9V-BATTERY-12PK';
    ProductsDS.Fields.FieldByName('description').AsString := '12-pack of 9-volt batteries';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 20;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 2;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := '9V-BATTERY-4PK';
    ProductsDS.Fields.FieldByName('description').AsString := '4-pack of 9-volt batteries';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 4.5;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 1.5;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'CALCULATOR-BUSINESS';
    ProductsDS.Fields.FieldByName('description').AsString := 'Business calculator';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 10;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 1;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'CASH-REGISTER';
    ProductsDS.Fields.FieldByName('description').AsString := 'Cash register with thermal printer';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 170;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 10;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'FLASH-USB-16GB';
    ProductsDS.Fields.FieldByName('description').AsString := '16GB USB flash drive';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 15;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 0.5;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'FLASH-USB-32GB';
    ProductsDS.Fields.FieldByName('description').AsString := '32GB USB flash drive';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 25;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 0.5;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'FLASH-USB-8GB';
    ProductsDS.Fields.FieldByName('description').AsString := '8GB USB flash drive';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 10;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 0.5;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'LABEL-MAKER';
    ProductsDS.Fields.FieldByName('description').AsString := 'Label maker - plastic labels';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 35;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 2;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'PEN-BP-12PK';
    ProductsDS.Fields.FieldByName('description').AsString := '12-pack of ballpoint pens';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 12;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 0.6;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'PHONE-HEADSET';
    ProductsDS.Fields.FieldByName('description').AsString := 'Hands-free phone headset';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 15;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 2;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'PHONE-SYSTEM-4HS';
    ProductsDS.Fields.FieldByName('description').AsString := '4-handset phone system with main base';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 120;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 4;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'PROJECTOR-HD';
    ProductsDS.Fields.FieldByName('description').AsString := '1080p HD Projector';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 850;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 56;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'SCANNER-SF';
    ProductsDS.Fields.FieldByName('description').AsString := 'Sheet-feed paper scanner';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 150;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 7;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'SHREDDER-SF-CC';
    ProductsDS.Fields.FieldByName('description').AsString := 'Sheet-feed, cross-cut shredder with bin';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 8;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 10;
    ProductsDS.Post;

    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := 'USB-CARD-READER';
    ProductsDS.Fields.FieldByName('description').AsString := 'USB magnetic strip card reader';
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := 25;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := 2;
    ProductsDS.Post;
  end;

  { fill Customer dataset }  
  fillProductsDS;

 

Dataset row JSON structure

DataSet Rows: Observe the following details on the column "dhData", when the SaveToString method was called. The field "dhData" was populated, of course this could be requested from the web server using HTTP GET request.

{
 "dhMagic" : 51966,
 "dhCount" : 15,
 "dhFieldDefs" : {
  "ddMagic" : 3401235116,
  "ddDefs" : [{
    "fdName" : "productid",
    "fdDatatype" : 4
   }, {
    "fdName" : "description",
    "fdDatatype" : 4
   }, {
    "fdName" : "listprice",
    "fdDatatype" : 3
   }, {
    "fdName" : "shipping",
    "fdDatatype" : 3
   }
  ]
 },
 "dhData" : [{
   "productid" : "9V-BATTERY-12PK",
   "description" : "12-pack of 9-volt batteries",
   "listprice" : 20,
   "shipping" : 2
  }, {
   "productid" : "9V-BATTERY-4PK",
   "description" : "4-pack of 9-volt batteries",
   "listprice" : 4.5,
   "shipping" : 1.5
  }, {
   "productid" : "CALCULATOR-BUSINESS",
   "description" : "Business calculator",
   "listprice" : 10,
   "shipping" : 1
  }, {
   "productid" : "CASH-REGISTER",
   "description" : "Cash register with thermal printer",
   "listprice" : 170,
   "shipping" : 10
  }, {
   "productid" : "FLASH-USB-16GB",
   "description" : "16GB USB flash drive",
   "listprice" : 15,
   "shipping" : 0.5
  }, {
   "productid" : "FLASH-USB-32GB",
   "description" : "32GB USB flash drive",
   "listprice" : 25,
   "shipping" : 0.5
  }, {
   "productid" : "FLASH-USB-8GB",
   "description" : "8GB USB flash drive",
   "listprice" : 10,
   "shipping" : 0.5
  }, {
   "productid" : "LABEL-MAKER",
   "description" : "Label maker - plastic labels",
   "listprice" : 35,
   "shipping" : 2
  }, {
   "productid" : "PEN-BP-12PK",
   "description" : "12-pack of ballpoint pens",
   "listprice" : 12,
   "shipping" : 0.6
  }, {
   "productid" : "PHONE-HEADSET",
   "description" : "Hands-free phone headset",
   "listprice" : 15,
   "shipping" : 2
  }, {
   "productid" : "PHONE-SYSTEM-4HS",
   "description" : "4-handset phone system with main base",
   "listprice" : 120,
   "shipping" : 4
  }, {
   "productid" : "PROJECTOR-HD",
   "description" : "1080p HD Projector",
   "listprice" : 850,
   "shipping" : 56
  }, {
   "productid" : "SCANNER-SF",
   "description" : "Sheet-feed paper scanner",
   "listprice" : 150,
   "shipping" : 7
  }, {
   "productid" : "SHREDDER-SF-CC",
   "description" : "Sheet-feed, cross-cut shredder with bin",
   "listprice" : 8,
   "shipping" : 10
  }, {
   "productid" : "USB-CARD-READER",
   "description" : "USB magnetic strip card reader",
   "listprice" : 25,
   "shipping" : 2
  }
 ]
}

 

Loading/Saving records

TW3Dataset allows you to save your data to a normal string or a stream.

function    SaveToString:String;
Procedure   LoadFromString(Const aText:String);
 
Procedure   SaveToStream(const Stream:TStream);virtual;
Procedure   LoadFromStream(const Stream:TStream);virtual;
So we can store a dataset locally with SaveToString/SaveToStream methods
and retrieve the data using LoadFromString/LoadFromStream methods.
Example:

ProductsDS := TDataset.Create;
ProductsDS.LoadFromString( jsonData );
ProductsDS.CreateDataset;
This will load locally a dataset. We can use it as intermediate bridge, we can insert, append, delete data locally, and push the data to a server. In this example, let's just list two Products fields:

ProductsDS.Active := true;

procedure ListProductsDS;
begin
ProductsDS.first;
while not ProductsDS.EOF do
begin
  var id  := ProductsDS.fields.fieldbyname('productid').asString;
  var price := ProductsDS.fields.fieldbyname('listprice').asString;
  writeln(id + ' ' + price);
  ProductsDS.Next;
end;
end;

{ List ProductsDS }
ListProductsDS;

/*
9V-BATTERY-12PK   20
9V-BATTERY-4PK   4.5
CALCULATOR-BUSINESS     10
CASH-REGISTER   170
FLASH-USB-16GB   15
FLASH-USB-32GB   25
FLASH-USB-8GB   10
LABEL-MAKER   35
PEN-BP-12PK   12
PHONE-HEADSET   15
PHONE-SYSTEM-4HS  120
PROJECTOR-HD   850
SCANNER-SF   150
SHREDDER-SF-CC   8
USB-CARD-READER  25
*/

 

Load remote data

Another nice feature is that you can define your dataset locally and load data remotely. Let's suppose our Delphi REST server is returning this:
{"rows" : [{
 "productid" : "9V-BATTERY-12PK",
 "description" : "12-pack of 9-volt batteries",
 "listprice" : 20,
 "shipping" : 2
}, {
 "productid" : "9V-BATTERY-4PK",
 "description" : "4-pack of 9-volt batteries",
 "listprice" : 4.5,
 "shipping" : 1.5
}, {
 "productid" : "CALCULATOR-BUSINESS",
 "description" : "Business calculator",
 "listprice" : 10,
 "shipping" : 1
}, {
 "productid" : "CASH-REGISTER",
 "description" : "Cash register with thermal printer",
 "listprice" : 170,
 "shipping" : 10
}, {
 "productid" : "FLASH-USB-16GB",
 "description" : "16GB USB flash drive",
 "listprice" : 15,
 "shipping" : 0.5
}, {
 "productid" : "FLASH-USB-32GB",
 "description" : "32GB USB flash drive",
 "listprice" : 25,
 "shipping" : 0.5
}, {
 "productid" : "FLASH-USB-8GB",
 "description" : "8GB USB flash drive",
 "listprice" : 10,
 "shipping" : 0.5
}, {
 "productid" : "LABEL-MAKER",
 "description" : "Label maker - plastic labels",
 "listprice" : 35,
 "shipping" : 2
}, {
 "productid" : "PEN-BP-12PK",
 "description" : "12-pack of ballpoint pens",
 "listprice" : 12,
 "shipping" : 0.6
}, {
 "productid" : "PHONE-HEADSET",
 "description" : "Hands-free phone headset",
 "listprice" : 15,
 "shipping" : 2
}, {
 "productid" : "PHONE-SYSTEM-4HS",
 "description" : "4-handset phone system with main base",
 "listprice" : 120,
 "shipping" : 4
}, {
 "productid" : "PROJECTOR-HD",
 "description" : "1080p HD Projector",
 "listprice" : 850,
 "shipping" : 56
}, {
 "productid" : "SCANNER-SF",
 "description" : "Sheet-feed paper scanner",
 "listprice" : 150,
 "shipping" : 7
}, {
 "productid" : "SHREDDER-SF-CC",
 "description" : "Sheet-feed, cross-cut shredder with bin",
 "listprice" : 8,
 "shipping" : 10
}, {
 "productid" : "USB-CARD-READER",
 "description" : "USB magnetic strip card reader",
 "listprice" : 25,
 "shipping" : 2
}
]}
The dataset rows must be loaded at run-time, and the data rows can come from the web server application in JSON format. When the rows are loaded, you can specify that the rows be appended to the existing rows in the dataset, or completely replace the current rows in the dataset.
The above example of the JSON for the Products dataset returned by a server.
We can fill the predefined dataset remotely, from a JSON string, for instance:
procedure fillProductsDS(strJSON: string);
begin
  var resultSet := JSON.Parse(strJSON).rows;
  for i in resultSet do
  begin
    ProductsDS.Append;
    ProductsDS.Fields.FieldByName('productid').AsString   := resultSet[i].productid;
    ProductsDS.Fields.FieldByName('description').AsString := resultSet[i].description;
    ProductsDS.Fields.FieldByName('listprice').AsFloat    := resultSet[i].listprice;
    ProductsDS.Fields.FieldByName('shipping').AsFloat     := resultSet[i].shipping;
    ProductsDS.Post;
  end;
end;

fillProductsDS(dataJSON);

Tags: TW3Dataset; in-memory datastore; JSON store.

domingo, 31 de julho de 2016

Generic Report with Smart Mobile Studio

How to create dynamically generic reports in Smart Pascal. Something like:

var                                    
   Report: BaseReport;                  
 begin                                  
   Report:= CreateGenericReport( <T> ); 
 end;

 

Does SmartMS supports generics?
Smart Pascal does not support generics. The original syntax derives from Delphi Web Script, was compatible with Delphi 7. Although the DWScript codebase and language has evolved considerably over the years, generics, represents a monumental change that might require a complete re-write of the entire parser and AST ("abstract symbolic tree") in compiler.

I'd try creating a single factory class in pure smart way, that does not uses generics to allow you to change the key (the key, in this case is a string representing the report name) used to request an report instance.  

You simply pass the key value and the static method that will be invoked to do the creation. Lastly, I want to retrieve a particular instance of my report by passing the key value, for instance.

Remember however, the type of the key value and the base type of the returned objects should be configurable.

LIVE PREVIEW

Let's say you have a reporting application. You may have a base report and a bunch of descendant reports, and you want to enforce some consistency over property values for the different reports that are set at the time of creation.

This is the base class TBaseReport:

unit uBaseReport;

interface

type
  TBaseReport = class(TObject)
  published
    function BuildReport: string; virtual;
end;

implementation

uses uReportA, uReportB, uReportC;

function TBaseReport.BuildReport: string;
begin
  Result := 'master report';
end;

end.

and three descendant classes:

unit uReportA;

interface

uses 
uBaseReport;

type
  TReportA = class(TBaseReport)
  published
    function BuildReport: string; override;
end;

implementation

function TReportA.BuildReport: string;
begin
  // TODO
end;

end.

 

unit uReporB;

interface

uses 
  uBaseReport;

type
  TReportB = class(TBaseReport)
  published
    function BuildReport: string; override;
end;

implementation

function TReportB.BuildReport: string;
begin
  // TODO
end;

end.

 

unit uReporC;

interface

uses 
  uBaseReport;

type
  TReportC = class(TBaseReport)
  published
    function BuildReport: string; override;
end;

implementation

function TReportC.BuildReport: string;
begin
  // TODO
end;

end.

 

The idea is try to centralise the creation logic for classes.

Having these spread around every different place a report could be created may be error-prone.

A Report Factory can give you one place to make sure all reports get created in the certain way, and also let the client code simply request a report by name and have the correct concrete report class created for them.

So, let's re-implement the uBaseReport unit

 

nit uBaseReport;

interface

uses W3C.Console;

type
TBaseReport = class(TObject)
  published
    function BuildReport: string; virtual;
  end;

  TModelClass = class of TBaseReport;

  TModelFactory = class
  public
    class function CreateModelFromID(const AID: string): TBaseReport;
    class function FindModelClassForId(const AID: string): TModelClass;
    class function GetModelClassID(AModelClass: TModelClass): string;
    class procedure RegisterModelClass(const AID: string;
      AModelClass: TModelClass);
  end;

implementation

uses uReportA, uReportB, uReportC;

{ TModelFactory }

type
  TModelClassRegistration = record
    ID: string;
    ModelClass: TModelClass;
  end;

var
  RegisteredModelClasses: array of TModelClassRegistration;

class function TModelFactory.CreateModelFromID(const AID: string): TBaseReport;
var
  ModelClass: TModelClass;
begin
  ModelClass :=  FindModelClassForId(AID);
  if ModelClass <> nil then
    Result := ModelClass.Create
  else
    Result := nil;
end;

class function TModelFactory.FindModelClassForId(
  const AID: string): TModelClass;
var
  i, Len: integer;
begin
  Result := nil;
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if RegisteredModelClasses[i].ID = AID then begin
      Result := RegisteredModelClasses[i].ModelClass;
      break;
    end;
end;

class function TModelFactory.GetModelClassID(AModelClass: TModelClass): string;
var
  i, Len: integer;
begin
  Result := '';
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if RegisteredModelClasses[i].ModelClass = AModelClass then begin
      Result := RegisteredModelClasses[i].ID;
      break;
    end;
end;

class procedure TModelFactory.RegisterModelClass(const AID: string;
  AModelClass: TModelClass);
var
  i, Len: integer;
begin
  Assert(AModelClass <> nil);
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if (RegisteredModelClasses[i].ID = AID)
      and (RegisteredModelClasses[i].ModelClass = AModelClass)
    then begin
      Assert(FALSE);
      exit;
    end;
  RegisteredModelClasses.SetLength(Len+1);
  
  RegisteredModelClasses[Len].ID := AID;
  RegisteredModelClasses[Len].ModelClass := AModelClass;
end;

function TBaseReport.BuildReport: string;
begin
  Result := 'master report';
end;

end.

Remember however, the type of the key value and the base type of the returned objects should be configurable.

In the initialization session let's fill the configurable data:

initialization
  var rec :TModelClassRegistration;
  rec.ID := 'devil_report';
  rec.ModelClass := TReportA;
  RegisteredModelClasses.Add(rec);
  
  rec.ID:= 'flowers_report';
  rec.ModelClass := TReportB;
  RegisteredModelClasses.Add(rec);
  
  rec.ID := 'cards_report';
  rec.ModelClass := TReportC;
  RegisteredModelClasses.Add(rec);

With that, we've got a very reusable Factory class,

Now, create a form, add the uBaseReport, SmartCL.Controls.Combobox, SmartCL.Controls.Memo units, and create config the ComboBox and Memo components:

 

unit Form1;

interface

uses 
  uBaseReport,
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.  Forms, 
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application,
  SmartCL.Controls.Combobox, SmartCL.Controls.Memo;

type
  TForm1 = class(TW3Form)
    procedure ComboBox1Changed(Sender: TObject);
  private
    {$I 'Form1:intf'}
    div1: TW3Memo;
    ComboBox1: TW3ComboBox;
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
    procedure Resize; override;
  end;

implementation

{ TForm1 }

procedure TForm1.ComboBox1Changed(Sender: TObject);
begin
  if ComboBox1.SelectedIndex >= 1 then
  begin
    var rpt:= TModelFactory.CreateModelFromID( ComboBox1.Items[ComboBox1.SelectedIndex] );
    div1.InnerHTML := rpt.BuildReport;
  end;
end;

procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components
end;

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
  div1 := TW3Memo.Create(Self);
  div1.Width := 264;
  div1.Top := 128;
  div1.StyleClass := 'memo';
  div1.Left := 104;
  div1.Height := 208;
  div1.Name := 'div1';

  ComboBox1 := TW3ComboBox.Create(Self);
  ComboBox1.Width := 264;
  ComboBox1.Top := 88;
  ComboBox1.Left := 104;
  ComboBox1.Height := 32;
  ComboBox1.Name := 'ComboBox1';
  ComboBox1.OnChanged := ComboBox1Changed;

  ComboBox1.BeginUpdate;
  ComboBox1.Clear;
  ComboBox1.Add('Select a report');
  ComboBox1.Add('devil_report');
  ComboBox1.Add('flowers_report');
  ComboBox1.Add('cards_report');
  ComboBox1.EndUpdate;
end;
 
procedure TForm1.Resize;
begin
  inherited;
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.

I should have no excuse for avoiding them in the future. Anyway, hopefully this little experiment has sparked some thoughts in you. 

 

Another idea, would be create stores, it would be nice you have for instance a generic data stores, you can switch from Delphi REST mORMot data store to in-memory data store, but this is another story.

just curious to see the javascript output:

var TObject = {
 $ClassName : "TObject",
 $Parent : null,
 ClassName : function (s) {
  return s.$ClassName
 },
 ClassType : function (s) {
  return s
 },
 ClassParent : function (s) {
  return s.$Parent
 },
 $Init : function () {},
 Create : function (s) {
  return s
 },
 Destroy : function (s) {
  for (var prop in s)
   if (s.hasOwnProperty(prop))
    delete s.prop
 },
 Destroy$ : function (s) {
  return s.ClassType.Destroy(s)
 },
 Free : function (s) {
  if (s !== null)
   s.ClassType.Destroy(s)
 }
}
var Exception = {
 $ClassName : "Exception",
 $Parent : TObject,
 $Init : function () {
  FMessage = ""
 },
 Create : function (s, Msg) {
  s.FMessage = Msg;
  return s
 }
}
function $W(e) {
 return e.ClassType ? e : Exception.Create($New(Exception), e.constructor.name + ", " + e.message)
}
function $NewDyn(c, z) {
 if (c == null)
  throw Exception.Create($New(Exception), "ClassType is nil" + z);
 var i = {
  ClassType : c
 };
 c.$Init(i);
 return i
}
function $New(c) {
 var i = {
  ClassType : c
 };
 c.$Init(i);
 return i
}
function $Is(o, c) {
 if (o === null)
  return false;
 return $Inh(o.ClassType, c);
};
function $Inh(s, c) {
 if (s === null)
  return false;
 while ((s) && (s !== c))
  s = s.$Parent;
 return (s) ? true : false;
}
var TApplication = {
 $ClassName : "TApplication",
 $Parent : TObject,
 $Init : function ($) {
  TObject.$Init($);
 },
 RunApp : function (Self) {
  var m = null,
  p = "",
  n = null;
  m = TModelFactory.FindModelClassForId(TModelFactory, "devil_report");
  console.log(TObject.ClassName(m));
  p = TModelFactory.GetModelClassID(TModelFactory, m);
  console.log(p);
  n = TModelFactory.CreateModelFromID(TModelFactory, "flowers_report");
  console.log(n);
  TBaseReport.showmsg(n, "this is the smart report");
 },
 Destroy : TObject.Destroy
};
var TModelFactory = {
 $ClassName : "TModelFactory",
 $Parent : TObject,
 $Init : function ($) {
  TObject.$Init($);
 },
 CreateModelFromID : function (Self, AID) {
  var Result = null;
  var ModelClass$1 = null;
  ModelClass$1 = TModelFactory.FindModelClassForId(Self, AID);
  if (ModelClass$1) {
   Result = TObject.Create($NewDyn(ModelClass$1, ""));
  } else {
   Result = null;
  }
  return Result
 },
 FindModelClassForId : function (Self, AID$1) {
  var Result = null;
  var i = 0;
  var Len = 0;
  Result = null;
  Len = RegisteredModelClasses.length;
  var $temp1;
  for (i = 0, $temp1 = Len; i < $temp1; i++) {
   if (RegisteredModelClasses[i].ID$2 == AID$1) {
    Result = RegisteredModelClasses[i].ModelClass;
    break;
   }
  }
  return Result
 },
 GetModelClassID : function (Self, AModelClass) {
  var Result = "";
  var i$1 = 0;
  var Len$1 = 0;
  Result = "";
  Len$1 = RegisteredModelClasses.length;
  var $temp2;
  for (i$1 = 0, $temp2 = Len$1; i$1 < $temp2; i$1++) {
   if (RegisteredModelClasses[i$1].ModelClass == AModelClass) {
    Result = RegisteredModelClasses[i$1].ID$2;
    break;
   }
  }
  return Result
 },
 Destroy : TObject.Destroy
};
var TBaseReport = {
 $ClassName : "TBaseReport",
 $Parent : TObject,
 $Init : function ($) {
  TObject.$Init($);
 },
 showmsg : function (Self, str) {
  console.log(str);
 },
 Destroy : TObject.Destroy
};
function Copy$TModelClassRegistration(s, d) {
 d.ID$2 = s.ID$2;
 d.ModelClass = s.ModelClass;
 return d;
}
function Clone$TModelClassRegistration($) {
 return {
  ID$2 : $.ID$2,
  ModelClass : $.ModelClass
 }
}
var TReportA = {
 $ClassName : "TReportA",
 $Parent : TBaseReport,
 $Init : function ($) {
  TBaseReport.$Init($);
 },
 Destroy : TObject.Destroy
};
var TReportB = {
 $ClassName : "TReportB",
 $Parent : TBaseReport,
 $Init : function ($) {
  TBaseReport.$Init($);
 },
 Destroy : TObject.Destroy
};
var TReportC = {
 $ClassName : "TReportC",
 $Parent : TBaseReport,
 $Init : function ($) {
  TBaseReport.$Init($);
 },
 Destroy : TObject.Destroy
};
var Application = null;
var RegisteredModelClasses = [], rec = {
 ID$2 : "",
 ModelClass : null
};
rec.ID$2 = "devil_report";
rec.ModelClass = TReportA;
RegisteredModelClasses.push(Clone$TModelClassRegistration(rec)); ;
rec.ID$2 = "cards_report";
rec.ModelClass = TReportB;
RegisteredModelClasses.push(Clone$TModelClassRegistration(rec)); ;
rec.ID$2 = "flowers_report";
rec.ModelClass = TReportC;
RegisteredModelClasses.push(Clone$TModelClassRegistration(rec)); ;

var m = null, p = "", n = null;
m = TModelFactory.FindModelClassForId(TModelFactory, "devil_report");
console.log(TObject.ClassName(m));

p = TModelFactory.GetModelClassID(TModelFactory, m);
console.log(p);

n = TModelFactory.CreateModelFromID(TModelFactory, "flowers_report");
console.log(n);

TBaseReport.showmsg(n, "this is the smart report");

quinta-feira, 17 de setembro de 2015

Delphi mORMot Video Sync with Websockets




sexta-feira, 17 de abril de 2015

Delphi mORMot + websockets = DO MACARENA




domingo, 29 de março de 2015

Playing cards with Delphi mORMot