ObjectPascal: Meet the HTML Fragment!
One concept that has been around and existed for many programming languages is the Transpiler, which takes in the source code from one language, and rebuilds it in the target language. In most cases, this term is larger associated with the JavaScript Transpilers, which first originated with a Java-to-JavaScript compiler in the form of the Google WebToolkit, which is actually still around til this day. The most popular being that of TypeScript. Does anybody actually code in pure JavaScript anymore? I once tried a Python variant called Pyjs which at the time more or less copied the same API that GWT had. It was really fun to program in Python and essentially compile it into JavaScript so that it could easily run in the browser. Most people were probably blissfully unaware that ObjectPascal out of all possible languages can also transpile to JavaScript.
What is an HTML Fragment?
Perhaps I should explain the concept of an HTML Fragment before getting into the technical details of how to create and use one. In it's most simplest of comparisons would be if your entire HTML page was like a window on your desktop, then an HTML Fragment is like a panel of sorts if you have ever done desktop programming. For those more on the web development spectrum, you might be thinking, like an iframe, it can sort of be compared to an iframe in that in the desktop application, this panel can be swapped through code to display another panel. That widgets in the panel, and the panel itself is one whole widget that is then attached inside the desktop application window where it needs to be. Some applications might use this for switching tabs for example, where each tab is a panel instance that is created when a new tab is created and then put into place when the user chooses that tab. However, unlike an iframe, the data in that instance is not wiped if you swap it out for another panel. The panel is always in memory unless it is freed, enabling it to be hidden when it doesn't need to be visible, and allowing it to be immediately shown when it is required by the application.
The Core API
Firstly, I'd like to mention that the main pas2js use can still include programming like either GWT or Pyjamas. However, the core library has a lot of very useful components which can make programming a website or web application feel more like you are developing a modern event-based desktop application. This post will not be referring to the core features in pas2js, but the much more useful in practice THTMLFragment. This was introduced in a more recent version of pas2js, and thus a lot of my old code written in pas2js that is either public or private has not been updated to use this new standard. With that said, I highly recommend using pas2js in this method, unless you have an even more high-level component library that you might be using. This is middle-tier high-level, where I'm sure larger, more easier to use component libraries can be built by using a THTMLFragement as their starting point.
I should also like to point out, that I am assuming you have a working Lazarus, or version of ObjectPascal that can use pas2js, this post is not a full introduction. I will make a post on that at a later date.
Basic Project Creation
Firstly, I'll explain my personal workflow when it comes to starting a pas2js project, as this article is an introduction to the THTMLFragment, I also feel I should explain how to go about creating a basic project for it.
Open Lazarus, if you have Pas2JSDsgn and Pas2jsComponents installed into your IDE, then you should see new components, along with several new project types. The project type we will be using today, will be the Web Browser Application. It will ask you to fill in some basic information about your new project. All of the defaults are perfectly fine for this example. Personally, I tend to select the Use Browser Application object, as I like to have it in a class. Although, in the near future that preference might actually change due to the introduction of the THTMLFragment Data Module.
There is one extra option we do need to set in the project itself, and it is to ensure that our resources are compiled directly into the JavaScript code, as the loader requires this when loading the resources. Open the Project Options from the Projects menu. In the tree, locate Compiler Options->Custom Options. At the end of the current options, place -JRjs.
Enter the THTMLFragment
Once you have an appropriate project created and opened, we now need to create a new Data Module. These are special unit types in ObjectPascal, which are also available on the desktop. The main purpose is to place all shared non-visible components, such as a shared data source, hence it's name of Data Module. It is being used here as THTMLFragment is a subclass of TDataModule. Let's create our new THTMLFragment now.
In Lazarus, click on New.... This will open a new dialog box. Under the Module folder, you should be able to see one called Pas2JS HTML Fragment Module. Use that option and click Ok.
You should now see the empty Data Module unit in your source code viewer, but you may have also noticed that it created a new form-like window. In this new Data Module form, we can place various components from the component palette, since we are using pas2js here, we can only use components which can compile into JavaScript.
Before we add any components, we should set some of the properties on our new Data Module. Some of these properties have been inherited by the TDataModule class, while others are part of the THTMLFragment. You should always select a Name here, as it makes referencing it much easier. The Name will also be used to construct the global class instance. The only other property that we want to set here is the HTMLFileName. Leave all the other properties at their defaults for this example.
The next step after choosing an HTMLFileName is to create this file as a proper fragment. This file doesn't require any <html>,<head>, or <body> tags. Instead it should contain exactly the HTML you'd like to see rendered with this fragment. For this example, let's create this form:
<div id="tmsg"></div>
<b>Test Title:</b><input type="text" id="ttitle"/><br/>
<b>Test Name:</b><input type="text" id="tname"/><br/>
<b>Test Data:</b><br/>
<textarea id="tdata"></textarea><br/>
<button id="T1Btn" class="btn btn-primary">Test Button</button>
<button id="T1PopBtn" class="btn btn-primary">Populate</button>
<button id="T1HideBtn" class="btn btn-primary">Hide Fragment</button>
Save this with the same filename you placed into the HTMLFileName property box. With that complete, it's time to place a component into our Data Module.
There is one specific component which we are looking for, in the Pas2Js tab locate the component for THTMLElementActionList. In my palette, it appears as the first one. Click onto it, and then click onto a place in the empty Data Module form. This will create a new instance of that component in the Data Module.
Now, here is where the magic starts. There aren't really any properties which we are interested in changing on this component, although do set a Name. We want to use one of it's actions. So, Right-Click the newly placed component and from the context-menu, select Create actions for HTML tags.... This will open a new dialog, and if you've been following this guide, it should populate all the actions in this dialog box with all the id= elements from the HTMLFileName file we created earlier. All that you need to do here is confirm the actions being added, which requires just pressing Ok.
Once the dialog box closes, you should now notice a bunch of new code added to the class, one new variable for each of the elements from our HTMLFileName. If you ever add or remove HTML elements from your HTMLFileName, you can just select that same option again to generate the new code for those new elements. However, do not simply just add the variables in yourself. These variables that were created are tied to the ActionList, much like how traditional components are tied to a desktop form.
If you are familiar with programming desktop components, then using these new variables should be second nature. For example, to set or retrieve the value of one of these, you can use the .Value property. The next thing these variables share in common with desktop components is their event-based nature. We added some buttons to the HTML Fragment, so let's see how we can get those to work.
Pushing Forward with Components
In the properties window, locate the properties for actT1Btn. Under ElementID you should see Events, select the checkbox next to the Click one. Next, open the Events tab. Here, click the button at the far-right of OnExecute. As you can imagine what this will actually do, it explains that we want our button here to listen for click events, and if it's clicked, then run this code. Do the same for the other two buttons. You should have three new event procedures in your source code viewer. Here is the finished example unit:
unit test1frag;
{$mode ObjFPC}
interface
uses
SysUtils, Classes, Rtl.HTMLActions, htmlfragment, Web;
type
{ TTest1 }
TTest1 = class(THTMLFragment)
ActionList: THTMLElementActionList;
actT1Btn: THTMLElementAction;
actT1HideBtn: THTMLElementAction;
actT1PopBtn: THTMLElementAction;
acttdata: THTMLElementAction;
acttmsg: THTMLElementAction;
acttname: THTMLElementAction;
actttitle: THTMLElementAction;
procedure actT1BtnExecute(Sender: TObject; Event: TJSEvent);
procedure actT1HideBtnExecute(Sender: TObject; Event: TJSEvent);
procedure actT1PopBtnExecute(Sender: TObject; Event: TJSEvent);
private
public
end;
var
Test1: TTest1;
implementation
{$R *.lfm}
{ TTest1 }
procedure TTest1.actT1BtnExecute(Sender: TObject; Event: TJSEvent);
begin
if acttname.Value = '' then
acttmsg.Value:='Name Please!'
else
begin
if actttitle.Value = '' then
actttitle.Value:='Untitled';
acttmsg.Value:='Your Data has been submitted, '+acttname.Value+'!';
end;
end;
procedure TTest1.actT1HideBtnExecute(Sender: TObject; Event: TJSEvent);
begin
Hide;
end;
procedure TTest1.actT1PopBtnExecute(Sender: TObject; Event: TJSEvent);
begin
actttitle.Value:='Populated Title!';
acttname.Value:='John Doe';
acttdata.Value:='Some long form data can be loaded into here.';
acttmsg.Value:='Data Population Complete.';
end;
end.
The only actual code we have written by this point are merely just event handlers. The majority of our application configuration is done in the IDE. There is one last part, and that is to have it render onto our main HTML page.
Making it All Work
This is by far the most easiest part. Firstly use an existing HTML template of your choosing from that fancy freelance designer you know, or just add <div id="content"></div> to the pre-made index.html. You should also ensure that the runtime is run as well. Here is the most simple HTML page for your web application:
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Minimum Pas2Js Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="Example.js"></script>
</head>
<body>
<div id="content"></div>
<script>
rtl.showUncaughtExceptions=true;
window.addEventListener("load", rtl.run);
</script>
</body>
</html>
There is some code that now needs to be added to our main application to initialize and then render our HTML Fragment, and this code is short:
Test1:=TTest1.Create(Nil);
Test1.ParentID:='content';
Test1.Show;
Here is a full version with the Web Browser Application wrapper:
program Example;
{$mode objfpc}
uses
BrowserApp, JS, Classes, SysUtils, Web, sandbox, test1frag;
type
TMyApplication = class(TBrowserApplication)
protected
procedure DoRun; override;
public
end;
procedure TMyApplication.DoRun;
begin
Test1:=TTest1.Create(Self);
Test1.ParentID:='content';
Test1.Show;
end;
var
Application : TMyApplication;
begin
Application:=TMyApplication.Create(nil);
Application.Initialize;
Application.Run;
end.
Conclusion
This is only how to use the THTMLFragment in it's most simple form. In a future post, I will dive into how you can make them fully data aware.
I recently added a new GuestBook which can also be found in the upper navigation. Please leave any comments or questions you might have on these posts there. I also began adding tags to existing posts, and all new posts will contain tags. A few new stand-alone pages have also been added to make it easier to find specific content.