Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / pas2js / 2019-09

Bootstrap & Helper techniques

4 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Michael Van Canneyt

Hello,

I'm probably kicking in an open door if I say that there are a lot of javascript libraries available on internet.

Likewise, saying that the Twitter Bootstrap CSS and accompagnying library are popular for responsive design is an understatement: https://getbootstrap.com/docs/4.3/getting-started/introduction/

One of the many available tables (grids) is bootstrap table: https://bootstrap-table.com/

But how to effectively use these libraries in pas2js ?

I've committed a set of class helper, in units libbootstrap, and libbootstraptable.

You can see a demo on:

https://www.freepascal.org/~michael/bootstrap/bootstraptable.html

These units contain class helpers around the TJQuery class, in unit libjquery.

The library and demo is not just useful if you want to use bootstrap.

It also shows how easy and useful it is to create class helpers for external classes (such as jQuery). Doing so makes these libraries easy to use. (I have finished a complete application in no time using this technique)

Additionally, they show how to implement a popular pattern in javascript:

Often, an object literal is used to pass options to a Jquery or other function that configures some class or component. In this, it is important to realize that having a member present or not can make a difference for the library.

The reason is that such libraries merge the options you pass in with a 'default' set of options: any option not specified in your option is replaced with the default.

Now, if you were to define these options as a record in pas2js:

   TBootstrapModalOptions = record
     show : boolean;
     focus : boolean;
     keyboard : boolean;
     backdrop : boolean;
   end;

then all members would be present as this is expected in Pascal. But the merge operation performed by the Javascript library would yield unexpected results:

All the fields in the record would be used even if you didn't specify a value for any of them.

The solution is to define a class:

   TBootstrapModalOptions = Class external name 'Object' (TJSObject)
     show : boolean;
     focus : boolean;
     keyboard : boolean;
     backdrop : boolean;
   end;

Note the name of 'Object' and the parent class of TJSObject.

When this class is instantiated, it creates a javascript object without any members. When you do
O:=TBootstrapModalOptions.New;
O.focus:=True;
...

You will have in effect created an object literal with just 1 field.

You could achieve the same effect with
new(['focus',true]);
But it is clear that this is neither type safe nor can you use code completion in the IDE.

Using the above technique, Pas2js and the IDE are happy, because you can use identifier completion and you know the types are correct. And the library is happy, because it received only the field for which you specified a value. The price to pay is of course the create statement to construct the object. (which one would not have with a record)

Enjoy,

Michael.

PS. My thanks to Mattias for pointing out an embryonic version of this possibility. A life saver which had to be shared !

Reply by email

warleyalex
to Michael Van Canneyt

OK. In short, we can achieve (pass on object argument) the same effect with:

var
  o: TTOptions;
  op: TTableOptions;
  rec: TShopItem;

begin
{ Test I - it is possible to pass a parameter "inline" without having to declare a variable of that type first,
but we don't have the code completion
}

  DoSomething1( New(['MyName','Test1','Price',1, 'Func', @Go]) );
  { Test II - inherited from TJSObject to access the constuctor }
  o := TTOptions.new;
  o.MyName:= 'Test2';
  o.Price:= 2;
  o.Func:= @Go;
  DoSomething2(o);
  { Test III - it is not necessary to inherite from TJSObject at all }
  op := TTableOptions(CreateObject);
  op.MyName:= 'Test3';
  op.Price:= 3;
  op.Func:= @Go;
  DoSomething3(op);
  { Test IV - using a factory function together with the record }
  DoSomething4(Options('Test4',4, @Go));
  { Test V - using record type }
  rec.MyName:= 'Test5';
  rec.Price:= 5;
  rec.Func:= @Go;
  DoSomething5(rec);
  { Test VI - using advanced record approach }
  DoSomething6( TMyShopItem.Create('Test6', 6, @Go) );
  { Test VII - using static array of record }
  //console.log(staticObjects[0].MyName );
 // console.log(staticObjects[0].Price );
  DoSomething7(staticObjects[0]);
//-
CODE------------------------------------------------------------------------
type
  TShopItem = record
  MyName : string;
  Price : Double;
  Func: TProcedure;
end;
type
  { TMyShopItem }
  TMyShopItem = record
  MyName : string;
  Price : Double;
  Func: TProcedure;
  constructor Create(Name: String; Price: Double; Func: Tprocedure);
end;
type
  TTOptions = class external name 'Object' (TJSObject)
    MyName : string;
    Price : Double;
    Func: TProcedure;
  end;
type
  TTableOptions = class external name 'TTableOptions'
    MyName : string;
    Price : Double;
    Func: TProcedure;
  end;
  var
    CreateObject : TJSObject external name '{}';
implementation
{ TMyShopItem }
constructor TMyShopItem.Create(Name: String; Price: Double; Func:
Tprocedure);
begin
  Self.MyName := Name;
  Self.Price  := Price;
  Self.Func   := Func;
end;
//---------
procedure Go;
begin
  console.log('GO!');
end;

var
staticObjects: array of TShopItem =
(
(MyName : 'Test7'; Price : 7 {; Func: @Go} ),
(MyName : 'Pencil'; Price : 15.75),
(MyName : 'Board'; Price : 42.96)

    );
type
  TName = record
    MyName: String;
    Price: Double;
    Func: TProcedure;
  end;
  function Options(Name: String; Price: double; Func: TProcedure ): TName;
  begin
    Result.MyName := Name;
    Result.Price := Price;
    Result.Func := Func;
  end;
  procedure DoSomething1(options: TJSObject);
  begin
    console.log(options);
  end;
  procedure DoSomething2(options: TTOptions);
  begin
    console.log(options);
  end;
  procedure DoSomething3(options: TTableOptions);
  begin
    console.log(options);
  end;
  procedure DoSomething4(theRec : TName);
  begin
    console.log(theRec);
  end;
  procedure DoSomething5(theRec : TShopItem);
  begin
    console.log(theRec);
  end;
  procedure DoSomething6(theRec : TMyShopItem);
  begin
    console.log(theRec);
  end;
  procedure DoSomething7(theRec : TShopItem);
  begin
    console.log(theRec);
  end;

Reply by email

Michael Van Canneyt
to warleyalex
warleyalex wrote
OK. In short, we can achieve (pass on object argument) the same effect with:

Yes.

Show quoted text 6 lines
begin { Test I - it is possible to pass a parameter "inline" without having to declare a variable of that type first, but we don't have the code completion } DoSomething1( New(['MyName','Test1','Price',1, 'Func', @Go]) );

Simple, but not typesafe and error prone. Compiler will not check anything.

{ Test II - inherited from TJSObject to access the constuctor } o := TTOptions.new;

Correct.

{ Test III - it is not necessary to inherite from TJSObject at all } op := TTableOptions(CreateObject);

I consider this a dirty hack involving a typecast :)

{ Test IV - using a factory function together with the record } DoSomething4(Options('Test4',4, @Go));

Correct. You could put this in an overloaded constructor.

{ Test VI - using advanced record approach } DoSomething6( TMyShopItem.Create('Test6', 6, @Go) );

Yes, but note that for a record, all fields will always be present, which is not always what you want (or what a library expects). That was the point I was trying to make in my mail.

Michael.

Reply by email