JS rant
If you've read my home page you may have noticed that I currently consider myself (mostly) a Perl/Pascal programmer. This choice may seem archaic and arbitrary, but it is an informed decision based on two decades of programming experience. I used many languages throughout my career, starting with Pascal and C++, then PHP, JS, Python, C and finally Perl and Pascal once again. From all these languages, I only seem to need Perl, Pascal, C and JS nowadays.
The last language I listed is problematic. JS is not the language I like, I barely tolerate it, but I had to do a lot of JS development in the past (and still do sometimes). It was never a shiny new JS/TS with npm modules that gets babeled, webpacked and minified into a file. It often was (and still is) either plain JS or JQuery loaded directly from source into the client's browser.
Side note: the "shiny new" technologies I listed are probably not even considered shiny anymore...
I don't like the ecosystem of modules, the dependency trees often get really crazy. I don't like most JS frameworks, they move too fast for me to keep up. The community as a whole seems to disregard backward compatibility. I don't even like writing in the language itself, and Typescript does not make it any better for me. That being said, it is de facto the language you need to use if you want your page to have any client-side scripts.
I tried to learn some Angular a couple of years ago, and even started writing a typical todo application in it, but after shelving my project I recently discovered that it does not build anymore. So instead of fixing my project, I decided to fix the underlying problem which seemed to be using JS in the first place.
Lazarus and pas2js
Lazarus is a IDE / RAD application for Free Pascal. I consider Lazarus a great tool to develop GUI applications. I already made a couple small ones and was always pleased with the result. It has UI for designing the forms graphically, which allows for quite fast development of the GUI. It has an abstraction layer over the widget set used in the final application, so it can be modified freely before compilation (Qt5 / Qt6 / GTK2 / Win32).
Why am I bringing Lazarus up in a web application blog? Because Free Pascal community has developed a Pascal to Javascript transpiler called pas2js. Yes, this tool takes Pascal code and produces equivalent Javascript code. Surprisingly, it supports most of language constructs, excluding obvious ones like pointer manipulation and some unimplemented ones like variants.
With proper setup, Lazarus can be used to build a single-page web applications just like desktop applications. This currently requires a custom set of controls which are specific to pas2js. This is far from ideal, but a web widgetset should eventually be developed for standard controls, so that it will be possible to compile standard GUI Lazarus applications with pas2js.
I chose to use pas2js-ws which contains custom web controls to use in Lazarus.
Subject of the experiment
To tinker with pas2js, I needed an idea for an application which is small, yet have enough complexity to pose any difficulty. I decided to make a small Trello clone, an application which lets you visually track some tasks as they change their status.
I decided to call the application Brulion, which means "Notebook" in Polish. It consists of boards, lanes and notes. Boards are just different views you can look at. Lanes are vertical spaces with a label, which are containers for notes. Notes are the actual content, and can be freely moved between lanes, which represents a task changing its status. This is not much more complicated than a regular todo app, but it is flexible enough to be used in more scenarios.
Writing the API
I did not intend to write the API in any unconventional way. I already wrote Whelk, a Perl API framework based on Kelp, which I consider a solid solution for REST APIs (duh). The API is available in https://github.com/bbrtj/perl-brulion-api, which only took me a couple of hours to write. It already contains all three types of models, a SQLite database, an API with data validation, and an OpenAPI spec.
Writing the frontend
Designing an application in Lazarus means that no HTML must be written by hand. The result is a bunch of HTML divs which have position: absolute
and top/left position in the document. Not surprising given that you can put your controls in the designer anywhere you want. Pas2js-ws controls provide abstractions for everything, so no interaction with the actual DOM elements is required. Everything happens in the Pascal realm of component objects.
I wanted to use minimal custom styles. CSS can be used to fix a lot of shortcomings of the controls designed in Lazarus (by using !important
, which is ugly), but I thought it's almost like cheating. The usability and look of pas2js-ws controls was far from ideal. I had to fork it and quite heavily hack on it, but I eventually managed to get it to look and work decently. I reserved CSS mostly for changing the look of the webpage, not its functionality.
It is also a bit hard to find a CSS framework that respects the position: absolute
elements designed in Lazarus. Every single one I tried, including very minimal ones, had some kind of problem that I found hard to get around, mostly around paddings, margins and font sizes. In the end, I gave up and decided to just add some basic colors and defaults to a separate CSS file.
I quickly found out that modals are an easy way to ask user for data in this model and they look decent. Every form which asks for input is presented as a modal, but only one at a time. Popups asking user for confirmation are also modals. In Pascal code, modals are just regular pas2js-ws forms, but they are created using ShowModal
instead of Show
.
Aligning variable amount of data on screen was tricky, since everything is positioned statically based on top/left offset. When new data arrives, a loop in code calculates positions for each of its elements, and the total size of its parent. It sounds complicated compared to just putting more elements inside a flexbox, but it was not that bad after all:
procedure TLaneFrame.ReAlign();
const
CExtraPanelHeight = 100;
var
LCurrentOffset: Integer;
I: Integer;
begin
inherited ReAlign();
if LanePanel = nil then exit;
LCurrentOffset := 0;
for I := 0 to LanePanel.ControlCount - 1 do begin
LanePanel.Controls[I].Top := LCurrentOffset;
LCurrentOffset += LanePanel.Controls[I].Height;
end;
// extra height for better look and some drop area
LanePanel.Height := LCurrentOffset + CExtraPanelHeight;
end;
Outside of UI-related challenges, the biggest obstacle was writing a system to actually communicate with the API and encode / decode the data from it. Not only is it time-consuming because of static typing, AJAX communication needed to be written using Pascal abstraction over JS' XmlHTTPRequest, and JSON needed to use a different, JS-specific backend which caused me some confusion. To fight the lack of inline callbacks in the stable FPC version, I came up with an idea of pipelines which are similar to promises, but also contain a handling code in their classes, and can chain to each other. For example deleting a board is a "confirm" UI pipeline followed by a "delete" API pipeline. With these pipelines, I could mix and match what happens in the UI and what is sent to the backend, which made the task much easier and more readable.
Below is a code for a simple pipeline deleting a lane from the system. It is chained after a UI pipeline that shows a confirmation dialog - in only ever starts if the previous pipeline succeeds (the confirm button is clicked).
procedure TDeleteLanePipeline.Finish(Sender: TObject);
begin
TBrulionState(GContainer.Services[csState]).Lanes.Remove(self.Data);
inherited;
end;
procedure TDeleteLanePipeline.Start(Sender: TObject);
begin
inherited;
GContainer.LanesApi.DeleteLane(@self.Finish, self.Data.Id);
end;
Once the basic skeleton of the code was done, I noticed that it is actually really easy to add more stuff to the program. With proper data models, API communication system, dependency injection container, pipelines and pas2js-ws fixes done, it became a walk in the park to just add the missing pieces of functionality.
The frontend code is available in https://github.com/bbrtj/pascal-brulion.
End result
The end result is usable, but far from perfect. It does everything I originally wanted it to do. I did not find anything that would be hard blocker, impossible to do with pas2js. Everything was just a matter of putting more effort into the code, either on the application level or on the level of pas2js-ws.
Probably the most pressing matter is making it responsive and look good on vertical devices. This is a part I completely skipped, the application will look more or less the same on both desktop and mobile. From my quick testing it seems like drag and drop is currently not working on mobile.
I've set up a demo instance which showcases the working Pascal web application. I consider this a success - I managed to create a frontend application without touching any Javascript code at all, and only some of its documentation.
Observations and fun facts
In development mode, most pas2js errors are shown in web console with a full stacktrace in the actual pascal code. It shows you which pascal line caused an error, which I find really impressive.
Pascal is a language that is known for really fast compilation. Compiling the application takes around 0.7 seconds on my machine, way less than it would take to compile a similar application written in Angular.
The resulting .js file is around 700 kilobytes in size and contains full pas2js RTL as well as the program itself.
Pas2js can be used to write applications by manually creating DOM elements in code. It can then be used to create a layout based on flexbox, be more responsive and look better. It would however require much more manual work with Pascal abstraction over JS code, and manual interaction with the DOM.
Even though pas2js-ws manages the title of the browser window based on the currently active form, it has no router - changing the URL of the page dynamically and loading the application in a different state based on the URL is not achievable easily and would require custom code.
Comments? Suggestions? Send to bbrtj.pro@gmail.com
Published on 2025-06-28