Jump to content

using variants


Recommended Posts

  • Moderators

I needed a super chart component and stumbled on 'zoomcharts', a nice js library with good looking chart types.

 

This library even includes a typescript file. However parsing it through the ts2pas tool results in a whopping 3000 lines of code.

A bit daunting and of course most of the exported functions will never be used by an average user.

 

The minimum unit for accessing this library cuts it all down to some 20 lines, looking like

unit Zoom;
 
interface
 
type
 
  JZoomCharts = class external
 
    Procedure PieChart(V: Variant); external 'PieChart';
    Procedure NetChart(V: Variant); external 'NetChart';
    Procedure FacetChart(V: Variant); external 'FacetChart';
    Procedure GeoChart(V: Variant); external 'GeoChart';
    Procedure TimeChart(V: Variant); external 'TimeChart';
 
    Constructor   Create;
    Destructor    Destroy;
  end;
 
var
 
  ZoomCharts external 'ZoomCharts' : JZoomCharts;
 
implementation
 
{$R 'zoomcharts.js'}
 
end.

.

The method of communicating with the main external functions is defined here using a Variant, which makes life easy.

It does negate Pascals strong typing though.

 

I needed to get my head around how to use Variants so the following are three different methods which can be used from the calling program

 

Try 1 : build a variant in javascript

procedure TForm1.W3Button5Click(Sender: TObject);
var
  V : Variant;
begin
  V := TVariant.CreateObject;
  asm
    var Sjs =
        {
            container: document.getElementById("OBJ5"),
            area: { height: 663 },
            data: {
                preloaded: {
                    values: [
                        [0, 100],
                        [1000, 200],
                        [2000, 300],
                        [3000, 400],
                        [4000, 500]
                    ],
                    unit: 's'
                },
                timestampInSeconds: true
            }
        };
    @V = Sjs;
  end;
 
  ZoomCharts.TimeChart(V);
end;

This works ok.

 

To get rid of the asm sections I used JSON which resulted in try2 :

(note : JSON requires specifically double quotes)

procedure TForm1.W3Button5Click(Sender: TObject);
var
  V, V1, V2, V3 : Variant;
begin
  V := TVariant.CreateObject;
  V1 := TVariant.CreateObject;
  V2 := TVariant.CreateObject;
  V3 := TVariant.CreateObject;
 
//V1 : container
  asm var e = document.getElementById("OBJ5"); @V1 = e; end;
  V.container := V1;
 
//V2 : area;
  V2.height := 663;
  V.area := V2;
 
//V3 : data
  V3 := TVariant.CreateObject;
  V3 := JSON.Parse('{                                 ' +
         '       "preloaded": {                             ' +
         '           "values": [                               ' +
         '               [0, 100],                              ' +
         '               [1000, 200],                        ' +
         '               [2000, 300],                        ' +
         '               [3000, 400],                        ' +
         '               [4000, 500]                         ' +
         '           ],                                             ' +
         '           "unit": "s"                                ' +
         '       },                                                 ' +
         '       "timestampInSeconds": "true"    ' +
         '}');
 
  showmessage(json.stringify(v3));
  V.data := V3;
 
  ZoomCharts.TimeChart(V);
end;
 

This works too.

 

Try3 was to try and re-introduce a semblance of strong typing using records :

type
 
  TXXX = record
  published
    values : array[0..4] of array[0..1] of integer;
    &unit : string;
  end;
 
  TYYY = record
  published
    preloaded : TXXX;
    timestampInSeconds : boolean;
  end;
 
procedure TForm1.W3Button5Click(Sender: TObject);
var
  V, V1, V2, V3 : Variant;
  R1 : TXXX;
  R2 : TYYY;
begin
  V := TVariant.CreateObject;
  V1 := TVariant.CreateObject;
  V2 := TVariant.CreateObject;
  V3 := TVariant.CreateObject;
 
//V1 : container
  asm var e = document.getElementById("OBJ5"); @V1 = e; end;
  V.container := V1;
 
//V2 : area;
  V2.height := 663;
  V.area := V2;
 
//V3 : data
  V3 := TVariant.CreateObject;
 
  R1.unit := 's';
  R1.values := [[0, 100], [1000, 200], [2000, 300], [3000, 400], [4000, 500]];
 
  R2.preloaded := R1;
  R2.timestampInSeconds := true;
 
  V3.data := R2;
 
//  showmessage(json.stringify(v3));
  V.data := V3;
 
  ZoomCharts.TimeChart(V);
end;

which is by far the most compact and understandable code.

However this doesn't work for anonymous object fields

 

.

Link to post
Share on other sites

There is a simpler way that does not involve using asm and preserves typing to the last moment: using an anonymous class

var Document external 'document' : Variant;
...

procedure TForm1.W3Button5Click(Sender: TObject);
begin
  ZoomCharts.TimeChart(class
      container = Document.getElementById("OBJ5");
      area = class
         height : Integer = 663  // typing is optional but can be used if you don't want type inference
      end;
      data = class
         preloaded = class
            values : array [0..4] of array [0..1] of Integer = [
               [0, 100],
               [1000, 200],
               [2000, 300],
               [3000, 400],
               [4000, 500]
            ];
            &unit = 's';
         end;
         timestampInSeconds = true;
      end;
   end);
end;

 This will generate quite optimal JS. If you do not specify the field type, it will be inferred.

 

If a field name is a reserved pascal name, like 'unit', you can use the usual escape by prefixing with '&'. If the field name contains non identifier characters, you can use external

   weirdFieldName = 'hello'; external 'weird@field.name';

You can also declare lambdas and functions in an anonymous class, sso JS code can pretty much be mapped 1:1 to Pascal by using Variant & Anonymous classes. The strong typing will still be there in expressions, can be explicited in field types, or by using more or less strict external classes.

Depending on how well defined (or not) the external JS objects you are interfacing, it sometimes does not make much sense trying to strong type everything, because the calls parameters or optional objects are messy anyway.

 

Also two quick notes:

  • for creating a new empty JS object, the most efficient form is just "new JObject", for instance "v := new JObject" with v a Variant, the resulting JS is 'v = {}'.
  • for an external class, the procedure only need the "external" qualifier if the JS name is different from the Pascal name, f.i. if you want TimeChart on the pascal side, while on the JS side it is "timeChart"

 

Link to post
Share on other sites

There's a potencial issue with array [0..4] of array [0..1] of Integer when you use an anonymous class.

procedure TForm1.W3Button5Click(Sender: TObject);
begin
  ZoomCharts.TimeChart(class
      container = Document.getElementById("OBJ5");
      area = class
         height : Integer = 663  // typing is optional but can be used if you don't want type inference
      end;
      data = class
         preloaded = class
            values : array [0..4] of array [0..1] of Integer = [
               [0, 100],
               [1000, 200],
               [2000, 300],
               [3000, 400],
               [4000, 500]
            ];
            &unit = 's';
         end;
         timestampInSeconds = true;
      end;
   end);
end;

The compiler will emit this JS:

      ZoomCharts.TimeChart({
         "data" : {
            "timestampInSeconds" : true
            ,"preloaded" : {
               "unit" : "s"
               ,"values" : [[4000,500],[1000,200],[2000,300],[3000,400],[4000,500]]
            }
         }
         ,"area" : {
            "height" : 350
         }
         ,"container" : document.getElementById("mycontainer")
      });

the correct is:

var 
  valor: array [0..4] of array [0..1] of Integer = [
                 [0,    100],
                 [1000, 200],
                 [2000, 300],
                 [3000, 400],
                 [4000, 500]
              ];
begin
ZoomCharts.TimeChart(
class
   container := vdocument.getElementById("mycontainer");
   area = class
     height: Integer = 350;
   end;
   data = class
     preloaded = class
       values := valor;
       &unit: String = 's';
     end;
   timestampInSeconds: boolean = true;
   end;
end);

the compiler will emit the expected JS:

 var valor = [[0,0],[0,0],[0,0],[0,0],[0,0]],
     valor = [[0, 100], [1000, 200], [2000, 300], [3000, 400], [4000, 500]];
      ZoomCharts.TimeChart({
         "data" : {
            "timestampInSeconds" : true
            ,"preloaded" : {
               "unit" : "s"
               ,"values" : valor
            }
         }
         ,"area" : {
            "height" : 350
         }
         ,"container" : document.getElementById("mycontainer")
      });
Link to post
Share on other sites
  • 2 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...