[Pas2js] Pas2JS-Widgets with LFM streaming
Sven Barth
pascaldragon at googlemail.com
Sat Jul 27 11:23:13 CEST 2019
Am 27.07.2019 um 09:33 schrieb Michael Van Canneyt:
>
>
> On Fri, 26 Jul 2019, Sven Barth via Pas2js wrote:
>
>> Hello together!
>>
>> I've experimented a bit with Pas2JS and the Pas2JS-Widgets by
>> heliosroots (
>> https://github.com/heliosroots/Pas2JS_Widget/tree/master ) and
>> implemented loading of unmodified LFM files. It's a bit cumbersome
>> for now, cause it needs changes in the project's HTML source as well
>> as the main project file for each form added, but as a proof of
>> concept it works. I've attached the patch that needs to be applied to
>> the Pas2JS-Widget master.
>
> Impressive :-)
Thanks, though without your streaming implementation it would not have
been possible that easily. ;)
>
>>
>> To use it you proceed as follows:
>> - make sure you have installed Pas2JSDsgn (from Pas2JS) and
>> Pas2JS_Designer_Package (from Pas2JS-Widgets)
>> - make sure you at least once opened Pas2JS_RTL (from Pas2JS) and
>> Pas2JS_Widget (from Pas2JS-Widgets)
>> - create a new "Web Browser Application"
>> - add Pas2JS_RTL and Pas2JS_Widget as requirements
>> - add new forms using New -> Module -> Web Form (Pas2JS)
>> - remove the overridden Loaded method from any forms
>> - replace the main program's code with this (of course adjust the
>> form/unit names to your needs):
>>
>> === code begin ===
>>
>> program tp2j;
>>
>> {$mode objfpc}
>>
>> uses
>> Forms, FormLoader, Interfaces, Unit1;
>>
>> procedure DoInit(aData: TObject);
>> begin
>> Application.Initialize;
>> Application.CreateForm(TWForm1, WForm1);
>> Application.Run;
>> end;
>>
>> begin
>> LoadForms([TWForm1], @DoInit, Nil);
>> end.
>>
>> === code end ===
>
> I think you can avoid this change by checking the 'Run rtl when all
> page resources are fully loaded' options when creating your project.
>
> This will change the script tag to
> window.addEventListener("load", rtl.run);
> so the program only runs when all forms are actually loaded, avoiding the
> need to wait for the load.
Sadly this does not work. I don't even see the browser fetching the
file... :'(
>
> The changes to the html could be implemented without too much effort
> in the IDE package.
>
> Another approach would be to simply embed the .lfm in the html page in a
> script tag.
I personally would prefer to keep them separated to make versioning easier.
However what might be interesting (even for further functionality) might
be to run the HTML page through a template processor (*cough* fptemplate
*cough*) and replace some tag with the contents of all forms. Or
something like that...
>
>>
>> - add a script tag in front of the project's script tag in the HTML
>> file: <script src="unit1.lfm" type="application/x-lazarus-form"
>> id="TWForm1.lfm"></script>
>> - play around with the form (add components (from the Pas2JS
>> component tab only!), add events, etc.)
>> - before compiling make sure that the project *does not* contain a
>> requirement of Pas2JS_Designer_Package (the IDE currently adds this
>> when adding a component)
>> - make sure that the *.LFM files are at the same location as the *.JS
>> file
>> - load your application in the browser -> you should now see your form
>>
>> A screenshot showing the resulting browser and the IDE is attached. :)
>
> Nice job :-)
Yes. Though I'd really like to know how TMS dealt with the sizes.
Currently the problem is when I do this on a 92 DPI Lazarus (my Win7 at
work) I get buttons and labels that are too small for their contents and
on my HighDPI Win10 the button and label looks huge compared to the
content... (obviously the widgets need to deal with the different
sizes/take into account the design DPI, but the principal problem of
WYSIWYG remains)
>
>>
>> This can obviously be further improved:
>> - adding support also for frames and data modules
>> - caching the converted object stream (so that it doesn't need to be
>> converted for each creation of a form)
>> - add some way of asynchronous loading?
>
> There is the dynload unit which can help in this.
I don't see a DynLoad unit. You mean Rtl.ScriptLoader and/or
Rtl.UnitLoader? It's the former my FormLoader unit is based on. But
loading the content itself is not the problem (I've done that part
already ;) ). My (conceptual) problem is that the code is usually
synchronous. E.g. take the default main program:
=== code begin ===
begin
Application.Initialize;
Application.CreateForm(TWForm1, WForm1);
Application.Run;
end.
=== code end ===
Or the way to show another form:
=== code begin ===
SomeVar := TWForm2.Create(Application);
SomeVar.Show;
=== code end ===
In both cases it makes no sense to continue with the code after
CreateForm or Create as the resource must be there to show something
useful. Currently the loading inside TCustomForm looks like this:
=== code begin ===
procedure TCustomForm.ProcessResource;
begin
if not InitResourceComponent(Self, TWForm) then
raise EResNotFound.CreateFmt(
rsFormResourceSNotFoundForResourcelessFormsCreateNew, [ClassName]);
end;
constructor TCustomForm.Create(AOwner: TComponent);
begin
//GlobalNameSpace.BeginWrite;
try
CreateNew(AOwner, 1); // this calls BeginFormUpdate, which is ended
in AfterConstruction
if (ClassType <> TWForm) and not (csDesigning in ComponentState) then
begin
//Include(FFormState, fsCreating);
try
ProcessResource;
finally
//Exclude(FFormState, fsCreating);
end;
end;
finally
//GlobalNameSpace.EndWrite;
end;
end;
=== code end ===
And InitResourceComponent is this:
=== code begin ===
function InitResourceComponent(Instance: TComponent; RootAncestor: TClass
): Boolean;
begin
Result := InitLazResourceComponent(Instance, RootAncestor);
end;
function InitLazResourceComponent(Instance: TComponent; RootAncestor: TClass
): Boolean;
function InitComponent(ClassType: TClass): Boolean;
var
ResName: String;
Stream: TStream;
BinStream: TMemoryStream;
Reader: TReader;
script: TJSElement;
begin
//DebugLn(['[InitComponent] ClassType=',ClassType.Classname,'
Instance=',DbgsName(Instance),' RootAncestor=',DbgsName(RootAncestor),'
ClassType.ClassParent=',DbgsName(ClassType.ClassParent)]);
Result := False;
if (ClassType = TComponent) or (ClassType = RootAncestor) then
Exit;
if Assigned(ClassType.ClassParent) then
Result := InitComponent(ClassType.ClassParent);
Stream := nil;
ResName := ClassType.ClassName;
(*************)
script := Document.getElementById(ResName + '.lfm');
if Assigned(script) and (script.textContent <> '') then
Stream := TStringStream.Create(script.textContent);
if Stream = nil then
Exit;
try
//DebugLn('Form Stream "',ClassType.ClassName,'"');
try
BinStream := TMemoryStream.Create;
try
ObjectTextToBinary(Stream, BinStream);
BinStream.Position := 0;
Reader := TReader.Create(BinStream);
try
Reader.ReadRootComponent(Instance);
finally
Reader.Free;
end;
finally
BinStream.Free;
end;
except
on E: Exception do begin
Writeln(Format(rsFormStreamingError,[ClassType.ClassName,E.Message]));
raise;//exit;
end;
end;
finally
Stream.Free;
end;
Result := True;
end;
begin
if Instance.ComponentState * [csLoading, csInline] <> []
then begin
// global loading not needed
Result := InitComponent(Instance.ClassType);
end
else try
//BeginGlobalLoading;
Result := InitComponent(Instance.ClassType);
//NotifyGlobalLoading;
finally
//EndGlobalLoading;
end;
end;
=== code end ===
In the block marked with (*************) I'd essentially need to load
the form's resource dynamically from the server (the loading itself
isn't the problem) and then wait until all has been received. My
knowledge in JS isn't as good yet as to know the correct approach for
that, my gut tells me that something akin to "async/await" would be
necessary here.
>
> Nice ! slowly we go ahead. And more surprises are still coming... :-)
Definitely looking forward to that. :)
Regards,
Sven
More information about the Pas2js
mailing list