I was curious about several things: (1) What does the JavaScript code emitted by the converter look like? (2) How do you use that code with external JavaScript libraries? and (3) Is it practical to write JavaScript in Pascal?
As a test, I took this snippet of JavaScript from the BoxCast example app described here:
Figure 1. JavaScript from boxcast.js.
var map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/satellite-streets-v10",
center: [-107.74, 37.92],
zoom: 9.0,
logoPosition: "bottom-right" });
This is typical JavaScript code. It creates a Map object by calling its
constructor, passing in a JavaScript object that specifies various things such as
the HTML element to associate with the map object, the location the map should display,
and so on. All very readable and concise. You can read about the Map class
and its options here.
Its source is here.So how would you do this in Pascal? Can the converter create the functional equivalent of this code from Pascal?
Since Map is in the mapbox-gl.js file, we need a way of referencing code that's external to what the converter will produce. This is roughly analogous to using an external dynamic library currently, where a Pascal interface unit provides declarations for the functions that the library exports. With a dynamic library, you usually run a parser on the library's header file (.h) to create the interface unit more or less automatically. Since JavaScript doesn't have header files, I just created a rudimentary interface unit manually. It looks like this:
Figure 2. Pascal interface unit for mapbox-gl.js
unit MapboxGL_Intf;
interface
{$modeswitch externalclass}
type
TMapOptions = record
container : string;
style : string;
doubleClickZoom : Boolean;
// center : array [0..1] of Double; //not supported yet
zoom : Double;
logoPosition : string;
end;
TMap = class external name 'mapboxgl.Map'
constructor New(Options : TMapOptions);
end;
implementation
end.
Note that instead of declaring functions as external, the way you would with
a dynamic library, it declares a JavaScript class as external.Also, the converter does not appear to support the array declaration yet, so that's commented out for now.
In order to convert this to JS, I hacked FPC's testpas2js.pp program, specifically the tcmodules.pas unit, and added a few additional test suites. Here's what the converter generates for Figure 2's Pascal code:
Figure 3. Interface unit converted to JavaScript.
rtl.module("MapboxGL_Intf", ["System"], function () {
var $mod = this;
this.TMapOptions = function (s) {
if (s) {
this.container = s.container;
this.style = s.style;
this.doubleClickZoom = s.doubleClickZoom;
this.zoom = s.zoom;
this.logoPosition = s.logoPosition;
} else {
this.container = "";
this.style = "";
this.doubleClickZoom = false;
this.zoom = 0.0;
this.logoPosition = "";
};
this.$equal = function (b) {
return (this.container === b.container) && ((this.style === b.style) && ((this.doubleClickZoom === b.doubleClickZoom) && ((this.zoom === b.zoom) && (this.logoPosition === b.logoPosition))));
};
};
});
No real surprises here. It uses rtl.module to register the module. Looking
around I found rtl.js in FPC trunk too (under utils/pas2js/dist).
Obviously you'll need to use rtl.js with anything the converter generates.The TMapOptions record converts to a JavaScript object, with a constructor that either creates a new blank object or initializes it from an object passed. There's also a function for comparing whether the object is equal to another object of the same type. Since TMap was declared external, no additional code was generated for it.
So how to use the interface unit? Here's what I came up with as the Pascal equivalent of Figure 1's JavaScript:
Figure 4. Pascal that uses interface unit.
program testmb1;
uses
MapboxGL_Intf;
var
options : TMapOptions;
map : TMap;
begin
options.container := 'map';
options.style := 'mapbox://styles/mapbox/satellite-streets-v10';
// options.center[0] := -107.74; //longitude
// options.center[1] := 37.92; //latitude
options.zoom := 9.0;
options.logoPosition := 'bottom-right';
map := TMap.New(options);
end.
Converted to JavaScript, it looks like this:Figure 5. Pascal that uses interface unit converted to JavaScript.
rtl.module("program", ["System", "MapboxGL_Intf"], function () {
var $mod = this;
this.options = new pas.MapboxGL_Intf.TMapOptions();
this.map = null;
$mod.$main = function () {
$mod.options.container = "map";
$mod.options.style = "mapbox:\/\/styles\/mapbox\/satellite-streets-v10";
$mod.options.zoom = 9.0;
$mod.options.logoPosition = "bottom-right";
$mod.map = new mapboxgl.Map(new pas.MapboxGL_Intf.TMapOptions($mod.options));
};
});
Note the use of "pas". This is an object in rtl.js that allows you
to reference your registered modules.Finally, does the generated JavaScript still work?
First I added the new JavaScript files to BoxCast's index.html file (including a dummy system.js that I created):
Figure 6. Detail of BoxCast's modified index.html file.
<script src="rtl.js"></script>
<script src="system.js"></script>
<script src="mapboxgl_intf.js"></script>
<script src="testmb1.js"></script>
Here's the new calling code to replace Figure 1's "native" JavaScript:Figure 7. New boxcast.js calling code.
rtl.run("program");
var map = pas.program.map; //so remaining BoxCast code can be used as is
map.setCenter([-107.74, 37.92]); //workaround since can't set yet in Pascal
With the modified index.html loaded into a browser, BoxCast still works
as before.
macpgmr (at) icloud (dot) com
First posted July 30, 2017.