diff --git a/demo/webwidget/designdemo/design.css b/demo/webwidget/designdemo/design.css new file mode 100644 index 0000000..4321837 --- /dev/null +++ b/demo/webwidget/designdemo/design.css @@ -0,0 +1,52 @@ +button[data-widget-class] { + background-repeat: no-repeat; + background-position: center; + width: 36px; + height: 36px; +} +#toolbar { + width: 80%; + background-color: #DDDDDD; + margin-bottom: 32px; + padding: 4px 4px 4px 4px; + margin-left: 30px; +} +#designpage { + background-color: #A0A0A0; + width: 80%; + height: 80vh; + margin-left: 30px; + margin-top: 32px; +} + +.designerActive { + position: relative; + border: 1px dashed #87cefa; +} + +.designerToolbar { + position: absolute; + top: 0px; + left: -18px; + height: 100%; + display: flex; + flex-direction: column; +/* justify-content: space-around; */ +} + +.designerDragHandle{ + margin-left: 1px; +} + +.designerPlaceholder { + border: 3px dotted black; + margin: 1em 1em 1em 1em; + height: 50px; +} + +.source { + display: flex; + width: 540px; + margin: 10px auto; + font-size: 12px; +} \ No newline at end of file diff --git a/demo/webwidget/designdemo/designdemo.html b/demo/webwidget/designdemo/designdemo.html new file mode 100644 index 0000000..439a07c --- /dev/null +++ b/demo/webwidget/designdemo/designdemo.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + Designdemo + + + +
+
+
+ Created using   pas2js. +   Sources:   Program   + unit. + +
+ + + diff --git a/demo/webwidget/designdemo/designdemo.lpi b/demo/webwidget/designdemo/designdemo.lpi new file mode 100644 index 0000000..2a103cd --- /dev/null +++ b/demo/webwidget/designdemo/designdemo.lpi @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + <UseAppBundle Value="False"/> + <ResourceType Value="res"/> + </General> + <CustomData Count="3"> + <Item0 Name="MaintainHTML" Value="1"/> + <Item1 Name="PasJSWebBrowserProject" Value="1"/> + <Item2 Name="RunAtReady" Value="1"/> + </CustomData> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + <Modes Count="0"/> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="lazwebwidgets"/> + </Item1> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="designdemo.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="designdemo.html"/> + <IsPartOfProject Value="True"/> + <CustomData Count="1"> + <Item0 Name="PasJSIsProjectHTMLFile" Value="1"/> + </CustomData> + </Unit> + <Unit> + <Filename Value="designer.pp"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="webideclient.pp"/> + <IsPartOfProject Value="True"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="designdemo"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="js"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <AllowLabel Value="False"/> + <CPPInline Value="False"/> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> + <CodeGeneration> + <TargetOS Value="browser"/> + </CodeGeneration> + <Linking> + <Debugging> + <GenerateDebugInfo Value="False"/> + <UseLineInfoUnit Value="False"/> + </Debugging> + </Linking> + <Other> + <CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/> + <CompilerPath Value="$(pas2js)"/> + </Other> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/demo/webwidget/designdemo/designdemo.lpr b/demo/webwidget/designdemo/designdemo.lpr new file mode 100644 index 0000000..9253345 --- /dev/null +++ b/demo/webwidget/designdemo/designdemo.lpr @@ -0,0 +1,35 @@ +program designdemo; + +{$mode objfpc} +{$DEFINE USEIDE} + +uses + browserapp, JS, Classes, SysUtils, Web, designer, webideclient; + +type + TMyApplication = class(TBrowserApplication) + Public + FDemo : TDesignDemo; + FIDEIntf : TIDEClient; + procedure doRun; override; + end; + +procedure TMyApplication.doRun; + +begin + FDemo:=TDesignDemo.Create(Self); + {$IFDEF USEIDE} + FIDEIntf:=TIDEClient.Create(Self); + FDemo.IDEClient:=FIDEintf; + FIDEIntf.RegisterClient; + {$ENDIF} +end; + +var + Application : TMyApplication; + +begin + Application:=TMyApplication.Create(nil); + Application.Initialize; + Application.Run; +end. diff --git a/demo/webwidget/designdemo/designer.pp b/demo/webwidget/designdemo/designer.pp new file mode 100644 index 0000000..71f16d3 --- /dev/null +++ b/demo/webwidget/designdemo/designer.pp @@ -0,0 +1,353 @@ +unit designer; + +{$mode objfpc} + +interface + +uses + Classes, SysUtils, libjquery, webwidget, htmlwidgets, contnrs, js, web, webideclient; + +Type + + { TRegisteredWidget } + + TRegisteredWidget = Class + Private + FClass : TWebwidgetClass; + FImageName : String; + Public + Constructor Create(aClass : TWebwidgetClass; aImageName : String); + Property WidgetClass : TWebwidgetClass Read FClass; + Property ImageName : String Read FimageName; + end; + + TWidgetButtonWidget = Class(TButtonWidget) + MyWidget : TRegisteredWidget; + end; + + TSortable = Class helper for TJQuery + Procedure sortable(Options : TJSObject); external name 'sortable'; overload; + Procedure sortable(Options : string); external name 'sortable'; overload; + end; + + { TDesignDemo } + + TDesignDemo = class(TComponent) + private + FIDEClient: TIDEClient; + procedure AddWidgetByName(aID: NativeInt; AName: String); + function CreateNewWidget(aParent: TCustomWebWidget; aClass: TCustomWebWidgetClass): TCustomWebWidget; + function DoActive(Event: TEventListenerEvent): boolean; + procedure DoCommandsReceived(Sender: TObject; aCommands: TJSArray); + procedure DoWidgetAddClick(Sender: TObject; Event: TJSEvent); + procedure SetIDEClient(AValue: TIDEClient); + function SortableOptions: TJSObject; + function StreamWidget(aWidget: TCustomWebWidget): String; + Public + FConfirmAdd : NativeInt; + FAddWidget : TRegisteredWidget; + FPage : TWebPage; + FToolBar : TContainerWidget; + FButtons : Array[1..10] of TButtonWidget; + FWidgetButtons : Array of TWidgetButtonWidget; + FRegisteredWidgets : TObjectList; + Constructor Create(aOwner : TComponent); override; + Destructor Destroy; override; + Procedure RegisterWidgets; + Procedure RegisterWidget(aClass : TWebWidgetClass; aImageName : String); + procedure SetAddMode(aRegisteredWidget: TRegisteredWidget); + Procedure FillToolBar; + Procedure SetupPage; + property IDEClient: TIDEClient Read FIDEClient Write SetIDEClient; + end; + +implementation + +Const + SSortableSelect = '#designpage, #designpage [data-ww-element-content]'; + +Type + + { TJumboWidget } + + TJumboWidget = class(TCustomTemplateWidget) + Public + Constructor Create(aOwner: TComponent); override; + end; +type + TWidgetHack = Class(TCustomWebWidget) + Property Element; + Property ElementID; + Property TopElement; + Property ContentElement; + end; + TPageHack = Class(TWebPage) + Property Element; + Property ElementID; + end; + +{ TJumboWidget } + +constructor TJumboWidget.Create(aOwner: TComponent); +begin + inherited Create(aOwner); + Template.Text:='<div class="jumbotron">'+sLineBreak+ + '<h1 class="display-4">Hello, world!</h1>'+sLineBreak+ + '<p class="lead">This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>'+sLineBreak+ + '<hr class="my-4">'+sLineBreak+ + '<p>It uses utility classes for typography and spacing to space content out within the larger container.</p>'+sLineBreak+ + '<p class="lead">'+sLineBreak+ + ' <a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>'+sLineBreak+ + '</p>'+sLineBreak+ + '</div>'; +end; + + +{ TRegisteredWidget } + +constructor TRegisteredWidget.Create(aClass: TWebwidgetClass; aImageName: String); +begin + FClass:=aClass; + FImageName:=aImageName; +end; + +{ TDesignDemo } + +function TDesignDemo.CreateNewWidget(aParent : TCustomWebWidget; aClass : TCustomWebWidgetClass) : TCustomWebWidget; + +begin + Result:=aClass.Create(FPage); + Result.Name:=Result.ClassName+IntToStr(FPage.ChildCount); + Result.Parent:=aParent; + Result.Refresh; + if Assigned(IDEClient) then + IDEClient.SendAction('create',New(['widget',Result.Name,'class',Result.ClassName])); + +end; + +function TDesignDemo.StreamWidget(aWidget : TCustomWebWidget) : String; + +Var + S: TBytesStream; + T : TStringStream; + +begin + T:=Nil; + S:=TBytesStream.Create(Nil); + try + S.WriteComponent(aWidget); + S.Position:=0; + T:=TStringStream.Create(''); + ObjectBinaryToText(S,T); + Result:=T.DataString; + finally + T.Free; + S.Free; + end; +end; +function TDesignDemo.DoActive(Event: TEventListenerEvent): boolean; + +Const + Toolbar = '<div class="designerToolbar">' + + '<div class="designerDragHandle ui-icon ui-icon-arrow-4"></div>' + + '<div class="designerDelete ui-icon ui-icon-trash"></div>' + + '</div>'; + +var + aParent,aNew : TCustomWebWidget; + aNewActive : TJSHTMLElement; + aNewWidget : TRegisteredWidget; + +begin + Result:=True; + JQuery('.designerActive').removeClass('designerActive'); + JQuery('.designerToolbar').remove(); + aNewActive:=TJSHTMLElement(event.target); + aParent:=FPage.FindWidgetByID(String(aNewActive.dataset[STopElementData])); + if (FAddWidget<>Nil) and (aParent<>Nil) then + begin + aNewWidget:=FAddWidget; + FAddWidget:=Nil; + if aParent<>Nil then + begin + aNew:=CreateNewWidget(aParent,aNewWidget.WidgetClass); + aNewActive:=TWidgetHack(aNew).TopElement; + if Assigned(TWidgetHack(aNew).ContentElement) then + JQuery(TWidgetHack(aNew).ContentElement).Sortable(SortableOptions); + jQuery(aNewActive).on_('click',@DoActive); + aParent:=aNew; + end; + end; + JQuery(aNewActive).AddClass('designerActive').prepend(toolbar); + if assigned(aParent) and Assigned(IDEClient) then + IDEClient.SendAction('select',New(['widget',aParent.Name,'class',aParent.ClassName,'state',StreamWidget(aParent)])); +end; + +procedure TDesignDemo.SetAddMode(aRegisteredWidget : TRegisteredWidget); + +begin + FAddWidget:=aRegisteredWidget; +end; + +procedure TDesignDemo.DoWidgetAddClick(Sender: TObject; Event: TJSEvent); +begin + SetAddMode((Sender as TWidgetButtonWidget).MyWidget); +end; + +procedure TDesignDemo.AddWidgetByName(aID : NativeInt; AName : String); + +Var + I : integer; + Btn : TWidgetButtonWidget; + +begin + I:=FRegisteredWidgets.Count-1; + While (i>=0) and Not SameText(TRegisteredWidget(FRegisteredWidgets[i]).WidgetClass.ClassName,aName) do + Dec(I); + if I<0 then exit; + SetAddMode(TRegisteredWidget(FRegisteredWidgets[i])); + FConfirmAdd:=aID; +end; + +procedure TDesignDemo.DoCommandsReceived(Sender: TObject; aCommands: TJSArray); + +var + J,P : TJSOBject; + aName : String; + aID : NativeInt; + I : integer; + +begin + for I:=0 to aCommands.Length-1 do + begin + J:=TJSObject(aCommands[i]); + aName:=String(J['name']); + aID:=NativeInt(J['id']); + p:=TJSObject(J['payload']); + case aName of + 'addWidget' : AddWidgetByName(aID,String(P['class'])); + end; + end; +end; + +procedure TDesignDemo.SetIDEClient(AValue: TIDEClient); +begin + if FIDEClient=AValue then Exit; + FIDEClient:=AValue; + if assigned(FIDEClient) then + begin + FIDEClient.OnCommands:=@DoCommandsReceived; + FIDEClient.StartCommandPolling; + FToolBar.Visible:=False; + end; +end; + +function TDesignDemo.SortableOptions: TJSObject; + +begin + Result:=New([ + 'items','> [data-ww-element-top]', + 'connectWith','[data-ww-element-content]', + 'placeholder','designerPlaceholder', + 'tolerance','pointer', +// 'containment',TPageHack(FPage).Element, +// 'handle','[data-ww-element-top]', + 'cancel','' + ]); +end; + +constructor TDesignDemo.Create(aOwner: TComponent); + +begin + inherited Create(aOwner); + FRegisteredWidgets:=TObjectList.Create; + RegisterWidgets; + FillToolBar; + SetUpPage; + // [] + JQuery(SSortableSelect).Sortable(SortableOptions); + jQuery('#designpage').on_('click','[data-ww-element-top]',@DoActive); + jQuery('#designpage').on_('click',@DoActive); +end; + +destructor TDesignDemo.Destroy; +begin + FreeAndNil(FRegisteredWidgets); + inherited Destroy; +end; + +procedure TDesignDemo.RegisterWidgets; + +begin + RegisterWidget(TButtonWidget,'button'); + RegisterWidget(TCheckBoxInputWidget,'checkbox'); + RegisterWidget(TRadioInputWidget,'radio'); + RegisterWidget(TTextInputWidget,'edit'); + RegisterWidget(TImageWidget,'image'); + RegisterWidget(TTextAreaWidget,'memo'); + RegisterWidget(TSelectWidget,'select'); + RegisterWidget(TContainerWidget,'container'); + RegisterWidget(TJumboWidget,'jumbo'); +end; + +procedure TDesignDemo.RegisterWidget(aClass: TWebWidgetClass; aImageName: String); +begin + FRegisteredWidgets.Add(TRegisteredWidget.Create(aClass,aImageName)); +end; + +procedure TDesignDemo.SetupPage; + +Const + ButtonClasses : Array[0..8] of string + = ('primary','secondary','success','danger','warning','info','light','dark','link'); + +var + I : Integer; + +begin + FPage:=TWebPage.Create(Self); + FPage.ElementID:='designpage'; + FPage.Refresh; + For I:=0 to 9 do + begin + FButtons[I]:=TWidgetButtonWidget.Create(FPage); + FButtons[I].Classes:='btn btn-'+ButtonClasses[I mod 9]; + FButtons[I].Text:='Button #'+IntToStr(I+1); + FButtons[I].Parent:=FPage; + FButtons[I].Refresh; + end; +end; + +procedure TDesignDemo.FillToolBar; + +Var + RW : TRegisteredWidget; + I : Integer; + Btn : TWidgetButtonWidget; + +begin + FToolBar:=TContainerWidget.Create(Self); + FToolbar.Styles.EnsureStyle('min-height','34px'); + FToolbar.Styles.RemoveStyle('width'); + FToolbar.Styles.RemoveStyle('height'); + FToolbar.ElementId:='toolbar'; + FToolbar.Refresh; + SetLength(FWidgetButtons,FRegisteredWidgets.Count); + For I:=0 to FRegisteredWidgets.Count-1 do + begin + RW:=TRegisteredWidget(FRegisteredWidgets[I]); + Btn:=TWidgetButtonWidget.Create(Self); + FWidgetButtons[I]:=Btn; + Btn.MyWidget:=RW; + Btn.Classes:='btn btn-light'; + Btn.Text:=''; + Btn.Parent:=FToolbar; + Btn.Styles.Add('background-image','url("widgets/'+RW.ImageName+'.png")'); + Btn.Refresh; + Btn.Data['widgetClass']:=RW.WidgetClass.ClassName; + Btn.OnClick:=@DoWidgetAddClick; + end; +end; + +end. + diff --git a/demo/webwidget/designdemo/webideclient.pp b/demo/webwidget/designdemo/webideclient.pp new file mode 100644 index 0000000..a160bc9 --- /dev/null +++ b/demo/webwidget/designdemo/webideclient.pp @@ -0,0 +1,232 @@ +unit webideclient; + +{$mode objfpc} + +interface + +uses + Classes, SysUtils, js, web; + +type + TIDEClient = Class; + + TIDEResponseHandler = Procedure (aCode : Integer; aCodeText : String; aPayload : TJSObject) of object; + + { TIDERequest } + + TIDERequest = Class(TObject) + Private + FXHR : TJSXMLHttpRequest; + FOnResponse: TIDEResponseHandler; + Procedure ProcessResponse; + procedure DoStateChange; + Public + Constructor Create(aMethod, aURl: String; aPayLoad: TJSObject; aOnResponse: TIDEResponseHandler); + end; + + { TIDEClient } + TCommandEvent = Procedure (Sender : TObject; aCommands : TJSArray) of object; + TActionEvent = Procedure (Sender : TObject; aID : Int64; aName : String; aPayload : TJSObject) of object; + + TIDEClient = Class(TComponent) + private + FActionID : NativeInt; + FOnActionResponse: TActionEvent; + FPollID : NativeInt; + FCommandPollInterval : Integer; + FClientID: NativeInt; + FIDEURL: String; + FOnCommands: TCommandEvent; + FLastPoll : TIDERequest; + FStartPolling : Boolean; + procedure DoCommandPoll; + procedure OnActionSent(aCode: Integer; aCodeText: String; aPayload: TJSObject); + procedure OnClientRegistered(aCode: Integer; aCodeText: String; aPayload: TJSObject); + procedure OnCommandsReceived(aCode: Integer; aCodeText: String; aPayload: TJSObject); + Public + Constructor Create(aOwner : TComponent); override; + Procedure RegisterClient; + Procedure UnRegisterClient; + Procedure StartCommandPolling; + Procedure StopCommandPolling; + Function GetNextID : NativeInt; + procedure SendAction(Const aName : String; aPayLoad : TJSObject); + Property IDEURL : String read FIDEURL Write FIDEURL; + Property ClientID : Int64 read FClientID Write FClientID; + Property CommandPollInterval : Integer Read FCommandPollInterval Write FCommandPollInterval; + Property OnCommands : TCommandEvent Read FOnCommands Write FOnCommands; + Property OnActionResponse : TActionEvent Read FOnActionResponse Write FOnActionResponse; + end; + +implementation + +{ TIDEClient } + + +procedure TIDEClient.DoCommandPoll; + +begin + if Not Assigned(FLastPoll) then + FLastPoll:=TIDERequest.Create('Get',IDEURL+'Command/'+IntToStr(ClientID)+'/',Nil,@OnCommandsReceived); +end; + +procedure TIDEClient.OnActionSent(aCode: Integer; aCodeText: String; aPayload: TJSObject); + +Var + aID : NativeInt; + aName : string; + aActionPayload : TJSObject; + +begin + if (aCode div 100)=2 then + begin + aID:=NativeInt(aPayLoad['id']); + aName:=String(aPayLoad['name']); + aActionPayLoad:=TJSObject(aPayLoad['payload']); + If Assigned(OnActionResponse) then + OnActionResponse(Self,aID,aName,aActionPayload); + end; +end; + +procedure TIDEClient.OnClientRegistered(aCode: Integer; aCodeText: String; aPayload: TJSObject); + +begin + if (aCode div 100)=2 then + begin + FClientID:=NativeInt(aPayload['id']); + if FStartPolling then + StartCommandPolling; + end + else + FClientID:=0; +end; + +procedure TIDEClient.OnCommandsReceived(aCode: Integer; aCodeText: String; aPayload: TJSObject); + +Var + A: TJSArray; + +begin + FLastPoll:=Nil; + if (aCode div 100)<>2 then + exit; + if Assigned(aPayload) and isArray(aPayload['commands']) then + begin + A:=TJSArray(aPayload['commands']); + if (A.Length>0) then + OnCommands(Self,A); + end; +end; + +constructor TIDEClient.Create(aOwner: TComponent); +begin + Inherited; + FLastPoll:=Nil; + IDEURL:='http://'+Window.location.hostname+':'+Window.location.port+'/IDE/'; +end; + +procedure TIDEClient.RegisterClient; + +Var + P : TJSObject; + Req : TIDERequest; + +begin + P:=New(['url',window.locationString]); + req:=TIDERequest.Create('POST',IDEURL+'Client',P,@OnClientRegistered); +end; + +procedure TIDEClient.UnRegisterClient; + +Var + Req : TIDERequest; + +begin + Req:=TIDERequest.Create('DELETE',IDEURL+'Client/'+IntToStr(ClientID),Nil,@OnClientRegistered); +end; + +procedure TIDEClient.StartCommandPolling; +begin + if ClientID<>0 then + FPollID:=Window.setInterval(@DoCommandPoll,FCommandPollInterval) + else + FStartPolling:=True; +end; + +procedure TIDEClient.StopCommandPolling; +begin + FStartPolling:=False; + if (FPollID>0) then + Window.clearInterval(FPollID); +end; + +function TIDEClient.GetNextID: NativeInt; +begin + Inc(FActionID); + Result:=FActionID; +end; + +procedure TIDEClient.SendAction(const aName: String; aPayLoad: TJSObject); + +Var + aAction : TJSObject; + aID : NativeInt; + req: TIDERequest; + +begin + aID:=GetNextID; + aAction:=New(['id',aID, + 'name',aName, + 'payload',aPayLoad]); + req:=TIDERequest.Create('POST',IDEURL+'Action/'+IntToStr(ClientID)+'/'+IntToStr(aID),aAction,@OnActionSent); +end; + +{ TIDERequest } + +procedure TIDERequest.ProcessResponse; + +var + P : TJSObject; + +begin + if ((FXHR.Status div 100)=2) and (FXHR.ResponseHeaders['Content-Type']='application/json') then + P:=TJSJSON.parseObject(FXHR.responseText) + else + P:=Nil; + if Assigned(FOnResponse) then + FOnResponse(FXHR.Status,FXHR.StatusText,P); +end; + +procedure TIDERequest.DoStateChange; +begin + case FXHR.readyState of + TJSXMLHttpRequest.DONE : + begin + if Assigned(FOnResponse) then + ProcessResponse; + Free; + end; + end; +end; + +constructor TIDERequest.Create(aMethod, aURl: String; aPayLoad: TJSObject; aOnResponse: TIDEResponseHandler); + +Var + S : String; + +begin + FOnResponse:=aOnResponse; + FXHR:=TJSXMLHttpRequest.New; + FXHR.open(aMethod,aURL); + if assigned(aPayload) then + S:=TJSJSON.Stringify(aPayload) + else + S:=''; + FXHR.setRequestHeader('Content-Type','application/json'); + FXHR.onreadystatechange:=@DoStateChange; + FXHR.send(S); +end; + + +end. + diff --git a/demo/webwidget/designdemo/widgets/button.png b/demo/webwidget/designdemo/widgets/button.png new file mode 100644 index 0000000..9af0a27 Binary files /dev/null and b/demo/webwidget/designdemo/widgets/button.png differ diff --git a/demo/webwidget/designdemo/widgets/checkbox.png b/demo/webwidget/designdemo/widgets/checkbox.png new file mode 100644 index 0000000..8100ad4 Binary files /dev/null and b/demo/webwidget/designdemo/widgets/checkbox.png differ diff --git a/demo/webwidget/designdemo/widgets/container.png b/demo/webwidget/designdemo/widgets/container.png new file mode 100644 index 0000000..ca9b60c Binary files /dev/null and b/demo/webwidget/designdemo/widgets/container.png differ diff --git a/demo/webwidget/designdemo/widgets/edit.png b/demo/webwidget/designdemo/widgets/edit.png new file mode 100644 index 0000000..9411424 Binary files /dev/null and b/demo/webwidget/designdemo/widgets/edit.png differ diff --git a/demo/webwidget/designdemo/widgets/image.png b/demo/webwidget/designdemo/widgets/image.png new file mode 100644 index 0000000..6ead802 Binary files /dev/null and b/demo/webwidget/designdemo/widgets/image.png differ diff --git a/demo/webwidget/designdemo/widgets/jumbo.png b/demo/webwidget/designdemo/widgets/jumbo.png new file mode 100644 index 0000000..5b1587e Binary files /dev/null and b/demo/webwidget/designdemo/widgets/jumbo.png differ diff --git a/demo/webwidget/designdemo/widgets/memo.png b/demo/webwidget/designdemo/widgets/memo.png new file mode 100644 index 0000000..1f5499d Binary files /dev/null and b/demo/webwidget/designdemo/widgets/memo.png differ diff --git a/demo/webwidget/designdemo/widgets/radio.png b/demo/webwidget/designdemo/widgets/radio.png new file mode 100644 index 0000000..4a58ea8 Binary files /dev/null and b/demo/webwidget/designdemo/widgets/radio.png differ diff --git a/demo/webwidget/designdemo/widgets/select.png b/demo/webwidget/designdemo/widgets/select.png new file mode 100644 index 0000000..c6725f7 Binary files /dev/null and b/demo/webwidget/designdemo/widgets/select.png differ diff --git a/demo/webwidget/nativedesign/frmmain.lfm b/demo/webwidget/nativedesign/frmmain.lfm new file mode 100644 index 0000000..7850e45 --- /dev/null +++ b/demo/webwidget/nativedesign/frmmain.lfm @@ -0,0 +1,1553 @@ +object MainForm: TMainForm + Left = 684 + Height = 541 + Top = 193 + Width = 698 + Caption = 'IDE Design demo' + ClientHeight = 541 + ClientWidth = 698 + OnCloseQuery = FormCloseQuery + OnCreate = FormCreate + OnShow = FormShow + LCLVersion = '2.1.0.0' + object PBottom: TPanel + Left = 0 + Height = 44 + Top = 497 + Width = 698 + Align = alBottom + ClientHeight = 44 + ClientWidth = 698 + TabOrder = 0 + object Project: TLabel + Left = 25 + Height = 17 + Top = 15 + Width = 39 + Caption = 'Project' + ParentColor = False + end + object FEProject: TFileNameEdit + Left = 80 + Height = 29 + Top = 8 + Width = 608 + FileName = '/home/michael/P2JS/trunk/packages/webwidget/designdemo/designdemo.html' + DialogTitle = 'Select Project HTML File' + Filter = 'HTML Files|*.html|All files|*.*' + FilterIndex = 0 + DefaultExt = '.html' + HideDirectories = False + ButtonWidth = 23 + NumGlyphs = 1 + Flat = True + Anchors = [akTop, akLeft, akRight] + MaxLength = 0 + TabOrder = 0 + OnEditingDone = DEProjectEditingDone + Text = '/home/michael/P2JS/trunk/packages/webwidget/designdemo/designdemo.html' + end + end + object TBWidgets: TToolBar + Left = 0 + Height = 36 + Top = 0 + Width = 698 + ButtonHeight = 34 + ButtonWidth = 34 + Caption = 'TBWidgets' + Images = ILWidgets + TabOrder = 1 + object TBGo: TToolButton + Left = 1 + Top = 2 + Action = AGo + end + object ToolButton1: TToolButton + Left = 73 + Height = 34 + Top = 2 + Caption = 'ToolButton1' + Style = tbsSeparator + end + object TBExternalGo: TToolButton + Left = 37 + Top = 2 + Action = AGoExternal + end + end + object PCDesigner: TPageControl + Left = 0 + Height = 461 + Top = 36 + Width = 698 + ActivePage = TSBrowser + Align = alClient + TabIndex = 0 + TabOrder = 2 + object TSBrowser: TTabSheet + Caption = 'Design browser' + end + object TSLog: TTabSheet + Caption = 'Log' + ClientHeight = 430 + ClientWidth = 688 + object MLog: TMemo + Left = 0 + Height = 430 + Top = 0 + Width = 688 + Align = alClient + Lines.Strings = ( + 'MLog' + ) + ScrollBars = ssAutoBoth + TabOrder = 0 + end + end + object TSInspector: TTabSheet + Caption = 'Inspector' + end + end + object ILWidgets: TImageList + Height = 32 + Width = 32 + left = 384 + top = 16 + Bitmap = { + 4C690B00000020000000200000004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000120000002D00000049000000490000002D000000124C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C704700000000210000007E0000 + 00CD000000FF000000FF000000FF000000FF000000FF000000FF000000CD0000 + 007D000000204C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047000000001400000090000000F9000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000F80000008E000000134C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C70470000000036000000E5000000FF000000FF000000FF0000 + 00F0000000A80000007C00000060000000600000007C000000A9000000F10000 + 00FF000000FF000000FF000000E4000000354C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C70470000000051000000F7000000FF000000FF000000DF0000005C0000 + 00064C7047004C7047004C7047004C7047004C7047004C704700000000060000 + 005D000000E0000000FF000000FF000000F6000000504C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 470000000036000000F7000000FF000000FF00000099000000074C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000080000009C000000FF000000FF000000F6000000354C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0014000000E5000000FF000000FF000000794C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047000000007B000000FF000000FF000000E4000000134C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0091000000FF000000FF0000009E4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C70470000000099000000FF000000FF0000008E4C70 + 47004C7047004C7047004C7047004C7047004C7047004C704700000000210000 + 00F9000000FF000000DF000000074C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C70470000000008000000E1000000FF000000F80000 + 00204C7047004C7047004C7047004C7047004C7047004C7047000000007E0000 + 00FF000000FF0000005C4C7047004C7047004C7047004C7047004C7047000000 + 00190000000C4C7047004C7047004C7047004C7047004C7047000000001B0000 + 0025000000044C7047004C7047004C7047000000005F000000FF000000FF0000 + 007D4C7047004C7047004C7047004C7047004C7047004C704700000000CF0000 + 00FF000000F0000000064C7047004C7047000000002C000000B6000000F30000 + 00FF000000FF000000B54C7047004C70470000000035000000D1000000FF0000 + 00FF000000EC000000644C7047004C70470000000007000000F1000000FF0000 + 00CD4C7047004C7047004C7047004C7047004C70470000000012000000FE0000 + 00FF000000A84C7047004C7047000000002B000000F1000000FB0000009D0000 + 006B00000078000000674C7047000000001D000000EF000000F5000000720000 + 0060000000E4000000FF000000434C7047004C704700000000AA000000FF0000 + 00FE000000124C7047004C7047004C7047004C7047000000002F000000FF0000 + 00FF0000007A4C7047004C704700000000AC000000FF000000644C7047004C70 + 47004C7047004C7047004C70470000000084000000FF000000764C7047004C70 + 470000000045000000FF000000BC4C7047004C7047000000007D000000FF0000 + 00FF0000002E4C7047004C7047004C7047004C70470000000049000000FF0000 + 00FF000000614C7047004C704700000000E9000000FD0000000A4C7047000000 + 0083000000990000009900000018000000B2000000FF000000354C7047004C70 + 470000000005000000FC000000EB4C7047004C70470000000062000000FF0000 + 00FF000000484C7047004C7047004C7047004C70470000000048000000FF0000 + 00FF000000624C7047004C704700000000F0000000FD000000094C7047000000 + 00BD000000F2000000FF00000028000000B7000000FF000000334C7047004C70 + 470000000005000000FC000000EF4C7047004C70470000000063000000FF0000 + 00FF000000474C7047004C7047004C7047004C7047000000002E000000FF0000 + 00FF0000007B4C7047004C704700000000C0000000FF000000604C7047004C70 + 47000000009B000000FF0000002800000089000000FF000000744C7047004C70 + 470000000045000000FF000000BC4C7047004C7047000000007E000000FF0000 + 00FF0000002D4C7047004C7047004C7047004C70470000000012000000FE0000 + 00FF000000A94C7047004C70470000000044000000FC000000F9000000930000 + 0062000000C8000000FF000000280000001B000000F4000000F5000000730000 + 0060000000E4000000FE000000454C7047004C704700000000AB000000FF0000 + 00FE000000114C7047004C7047004C7047004C7047004C704700000000CD0000 + 00FF000000F3000000074C7047004C7047000000004A000000CF000000FF0000 + 00FF000000FF000000D60000001B4C70470000000043000000DC000000FF0000 + 00FF000000E4000000594C7047004C70470000000007000000F2000000FF0000 + 00CB4C7047004C7047004C7047004C7047004C7047004C7047000000007D0000 + 00FF000000FF0000005E4C7047004C7047004C7047004C7047000000000C0000 + 001D000000074C7047004C7047004C7047004C7047004C7047000000001F0000 + 0021000000014C7047004C7047004C70470000000060000000FF000000FF0000 + 007C4C7047004C7047004C7047004C7047004C7047004C704700000000200000 + 00F8000000FF000000E0000000084C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C70470000000009000000E2000000FF000000F80000 + 001F4C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 008E000000FF000000FF0000009F4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047000000009B000000FF000000FF0000008C4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0013000000E4000000FF000000FF0000007B4C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047000000007C000000FF000000FF000000E3000000124C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 470000000035000000F6000000FF000000FF0000009C000000084C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000090000009F000000FF000000FF000000F6000000344C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C70470000000050000000F6000000FF000000FF000000E10000005E0000 + 00074C7047004C7047004C7047004C7047004C7047004C704700000000070000 + 0060000000E2000000FF000000FF000000F6000000504C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C70470000000035000000E4000000FF000000FF000000FF0000 + 00F2000000A90000007D00000061000000610000007E000000AA000000F20000 + 00FF000000FF000000FF000000E3000000344C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C704700000000130000008E000000F8000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000F80000008D000000124C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C704700000000200000007D0000 + 00CD000000FF000000FF000000FF000000FF000000FF000000FE000000CC0000 + 007C0000001F4C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000120000002D00000049000000490000002C000000114C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047000000000C00000027000000280000000B4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047000000000D0000005E0000 + 00AC000000CF000000BF000000A4000000A4000000C0000000CF000000AC0000 + 005C0000000D4C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C704700000000060000006E000000CF000000770000 + 00274C7047004C7047000000000A0000000C4C7047004C704700000000280000 + 0079000000CF0000006C000000054C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047000000001E000000C6000000780000000A000000240000 + 00110000004B000000290000002C0000003200000022000000520000000D0000 + 00270000000A0000007B000000C60000001D4C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C70470000000035000000CF000000360000001A0000001E0000002B0000 + 00134C7047004C7047004C7047004C7047004C7047004C704700000000100000 + 002F000000180000001C00000037000000CE000000344C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47000000001E000000CF0000001E000000010000001C0000001F4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47000000001F000000214C7047000000001F000000CF0000001D4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0006000000C6000000364C704700000000724C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C704700000000720000000200000038000000C5000000054C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 006D0000007A0000001C000000224C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047000000001C0000001B0000007B0000006C4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047000000000D0000 + 00CF0000000A000000180000001D4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047000000001F0000001E0000000B000000CF0000 + 000C4C7047004C7047004C7047004C7047004C7047004C7047000000005D0000 + 007800000028000000304C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047000000002900000025000000790000 + 005C4C7047004C7047004C7047004C7047004C7047004C704700000000AD0000 + 00270000000C0000000F4C7047004C7047004C7047000000004A000000A00000 + 00B000000072000000094C7047004C7047000000000700000074000000AD0000 + 009F000000494C7047004C7047004C70470000000014000000120000002A0000 + 00AA4C7047004C7047004C7047004C7047004C7047004C704700000000CF4C70 + 4700000000524C7047004C7047004C70470000000067000000F80000008B0000 + 0072000000DC000000B14C70470000000001000000BE000000DD000000750000 + 008E000000F9000000664C7047004C7047004C7047000000004A4C7047000000 + 00CF4C7047004C7047004C7047004C7047004C7047000000000E000000BD4C70 + 4700000000214C7047004C70470000000002000000EC000000724C7047004C70 + 470000000025000000AA0000001100000049000000FA000000214C7047004C70 + 470000000077000000EC000000024C7047004C704700000000294C7047000000 + 00C00000000C4C7047004C7047004C7047004C70470000000028000000A30000 + 000C000000334C7047004C70470000000020000000FF0000002B4C7047000000 + 002300000033000000330000000E0000007A000000D14C7047004C7047004C70 + 47000000002C000000FF0000001F4C7047004C7047000000002C000000090000 + 00A6000000254C7047004C7047004C7047004C70470000000026000000A50000 + 00090000002B4C7047004C70470000000020000000FF0000002C4C7047000000 + 008C000000CC000000FE0000004400000079000000D14C7047004C7047004C70 + 47000000002D000000FF0000001F4C7047004C704700000000320000000C0000 + 00A6000000254C7047004C7047004C7047004C7047000000000D000000BE4C70 + 4700000000294C7047004C70470000000002000000EC000000764C7047004C70 + 470000000021000000FF0000002200000049000000FA000000224C7047004C70 + 470000000078000000EA000000024C7047004C704700000000224C7047000000 + 00C10000000B4C7047004C7047004C7047004C7047004C704700000000CF4C70 + 47000000004A4C7047004C7047004C70470000000068000000FA0000008F0000 + 0072000000D8000000B24C70470000000001000000BF000000DE000000760000 + 0090000000FA000000654C7047004C7047004C704700000000524C7047000000 + 00CF4C7047004C7047004C7047004C7047004C7047004C704700000000AC0000 + 002800000012000000134C7047004C7047004C7047000000004B0000009F0000 + 00AE00000073000000074C7047004C7047000000000700000074000000AD0000 + 009E000000474C7047004C7047004C704700000000100000000C0000002C0000 + 00A84C7047004C7047004C7047004C7047004C7047004C7047000000005D0000 + 007800000025000000284C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C70470000000030000000270000007B0000 + 005A4C7047004C7047004C7047004C7047004C7047004C7047000000000D0000 + 00CF0000000A0000001E0000001F4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047000000001E000000180000000B000000CF0000 + 000C4C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 006D0000007A0000001A0000001C4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C704700000000220000001B0000007E0000006A4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0006000000C50000003800000002000000714C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C704700000000724C70470000000038000000C4000000054C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47000000001D000000CE0000001E4C704700000000210000001F4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000200000001C000000010000001F000000CE0000001C4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C70470000000033000000CE000000370000001C00000018000000300000 + 00104C7047004C7047004C7047004C7047004C7047004C704700000000130000 + 002A0000001E0000001900000039000000CE000000334C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047000000001C000000C50000007C0000000B000000270000 + 000D0000005300000021000000330000002C000000290000004A000000110000 + 00240000000B0000007D000000C40000001C4C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C704700000000050000006B000000CF0000007A0000 + 002A4C7047004C7047000000000C000000094C7047004C7047000000002B0000 + 007A000000CF0000006A000000054C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047000000000C0000005B0000 + 00AB000000CF000000C1000000A4000000A5000000C1000000CF000000A90000 + 005B0000000C4C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047000000000B00000027000000260000000A4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C704700000000070000008A000000E6000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000E600000089000000060000008A000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF00000088000000E6000000FF0000003A4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47000000003B000000FF000000E5000000FF000000FF4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C7047000000004B000000E4000000E3000000494C7047004C7047004C70 + 47004C7047000000004B000000E4000000E3000000494C7047004C7047004C70 + 47004C7047000000004B000000E4000000E3000000494C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C704700000000E3000000FF000000FF000000E24C7047004C7047004C70 + 47004C704700000000E3000000FF000000FF000000E24C7047004C7047004C70 + 47004C704700000000E3000000FF000000FF000000E24C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C704700000000E3000000FF000000FF000000E24C7047004C7047004C70 + 47004C704700000000E3000000FF000000FF000000E24C7047004C7047004C70 + 47004C704700000000E3000000FF000000FF000000E24C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C70470000000049000000E3000000E2000000484C7047004C7047004C70 + 47004C70470000000049000000E3000000E2000000484C7047004C7047004C70 + 47004C70470000000049000000E3000000E2000000484C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C704700000000FF000000FF000000FF000000FF4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C704700000000FF000000FF000000E6000000FF0000003B4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47000000003D000000FF000000E500000088000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000860000000600000089000000E5000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000E500000087000000064C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0015000000AC000000F4000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000F4000000AC000000144C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00AC000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000AB4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00F4000000FF000000E2000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000E2000000FF000000F34C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0006000000A3000000394C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C704700000000060000 + 00B1000000FF000000F1000000384C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000006000000AF0000 + 00FF000000FF000000E80000002A4C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C70470000000006000000AF000000FF0000 + 00FF000000E80000002A4C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C70470000000006000000AF000000FF000000FF0000 + 00E80000002A4C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047000000000E000000504C7047004C70 + 47004C7047004C70470000000006000000B1000000FF000000FF000000E50000 + 00274C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047000000000E000000C7000000FE000000694C70 + 47004C70470000000006000000B0000000FF000000FF000000E80000002A4C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C70470000000050000000FD000000FF000000FD0000 + 006300000006000000B1000000FF000000FF000000E5000000264C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C70470000000063000000FD000000FF0000 + 00FD000000CD000000FF000000FF000000E5000000264C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C70470000000063000000FD0000 + 00FF000000FF000000FF000000E5000000264C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C704700000000630000 + 00FD000000FF000000E5000000264C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047000000 + 0062000000E60000002A4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47000000000C4C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000A94C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000A9000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00F4000000FF000000E2000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000E2000000FF000000F34C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00AB000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000AA4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0014000000AB000000F3000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000F3000000AA000000144C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000120000002D000000490000004E0000003C000000144C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C704700000000210000007E0000 + 00CD000000FF000000FF000000FF000000FF000000FF000000FF000000CB0000 + 00760000001E4C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047000000001400000090000000F9000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000F90000008B0000000F4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C70470000000036000000E5000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000E90000003D4C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C70470000000051000000F7000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000F90000005F4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 470000000032000000F5000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FA000000514C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0014000000E5000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FA000000524C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0090000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FA000000524C7047004C7047000000000F0000006E4C70 + 47004C7047004C7047004C7047004C7047004C7047004C704700000000210000 + 00F9000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FA000000524C7047004C70470000000010000000C9000000FB0000 + 00254C7047004C7047004C7047004C7047004C7047004C7047000000007F0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FA000000514C7047004C70470000000010000000C9000000FF000000FF0000 + 00864C7047004C7047004C7047004C7047004C7047004C704700000000CE0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FA0000 + 00524C7047004C70470000000012000000CC000000FF000000FF000000FF0000 + 00DA4C7047004C7047004C7047004C7047004C70470000000012000000FE0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000ED000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FA000000524C70 + 47004C70470000000013000000CE000000FF000000FF000000FF000000FF0000 + 00FE000000144C7047004C7047004C7047004C7047000000002E000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000800000001C000000DB0000 + 00FF000000FF000000FF000000FF000000FF000000FA000000524C7047004C70 + 470000000014000000CF000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000003B4C7047004C7047004C7047004C70470000000048000000FF0000 + 00FF000000FF000000FF000000FF000000A84C7047004C7047000000001C0000 + 00DB000000FF000000FF000000FF000000FA000000524C7047004C7047000000 + 0015000000D1000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000004C4C7047004C7047004C7047004C70470000000048000000FF0000 + 00FF000000FF000000FF000000FF000000FA000000514C7047004C7047000000 + 0018000000D6000000FF000000FA000000514C7047004C704700000000150000 + 00D2000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000474C7047004C7047004C7047004C7047000000002E000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FA000000514C7047004C70 + 470000000018000000D1000000514C7047004C70470000000016000000D20000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000002D4C7047004C7047004C7047004C70470000000012000000FE0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FA000000514C70 + 47004C704700000000044C7047004C70470000000018000000D5000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FE000000114C7047004C7047004C7047004C7047004C704700000000CD0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FC0000 + 00584C7047004C7047004C70470000000017000000D4000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00CB4C7047004C7047004C7047004C7047004C7047004C7047000000007E0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FC000000584C70470000000018000000D5000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 007C4C7047004C7047004C7047004C7047004C7047004C704700000000200000 + 00F8000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FA00000069000000DA000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000F80000 + 001F4C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 008E000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF0000008C4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0013000000E4000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000E3000000124C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 470000000032000000F5000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000F7000000364C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C70470000000050000000F6000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000F6000000504C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C70470000000030000000E1000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000E5000000354C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C704700000000130000008E000000F8000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000F80000008D000000124C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C704700000000200000007D0000 + 00CD000000FF000000FF000000FF000000FF000000FF000000FE000000CC0000 + 007C0000001F4C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000120000002D00000049000000490000002C000000114C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047000000000D0000 + 007B000000A7000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000A70000007A0000 + 000C4C7047004C7047004C7047004C7047004C70470000000004000000CA0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00C9000000044C7047004C7047004C7047004C7047000000003C000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000003A4C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047000000 + 00AA000000AA0000009C0000000E4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000FF000000CC000000114C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000FF000000FF000000CC000000114C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047000000003B000000FF0000 + 00FF000000FF000000FF000000FF000000B7000000084C7047004C7047000000 + 0077000000FF000000FF000000FF000000FF000000CC000000114C7047004C70 + 470000000063000000FD000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000394C7047004C7047004C7047004C70470000000004000000C90000 + 00FF000000FF000000FF000000FF000000FF000000B3000000074C7047004C70 + 470000000077000000FF000000FF000000FF000000FF000000CC000000114C70 + 47004C70470000000066000000FE000000FF000000FF000000FF000000FF0000 + 00C8000000044C7047004C7047004C7047004C7047004C7047000000000C0000 + 0079000000A7000000AA000000AA000000AA000000AA0000005F4C7047004C70 + 47004C70470000000077000000FF000000FF000000FF000000FF000000CC0000 + 00114C7047004C70470000000060000000AA000000AA000000A7000000790000 + 000C4C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C70470000000077000000FF000000FF000000FF000000FF0000 + 00CC000000114C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C70470000000080000000FF000000FF000000FF0000 + 00F5000000394C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C70470000000080000000FF000000F50000 + 0043000000130000009C0000000E4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047000000007B000000430000 + 0013000000D0000000FF000000BF4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047000000000A0000 + 00D0000000FF000000FF000000AB4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0077000000FF000000B6000000074C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 470000000038000000054C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047000000000D0000 + 007A000000A7000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000A70000007A0000 + 000C4C7047004C7047004C7047004C7047004C70470000000004000000CA0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00C9000000044C7047004C7047004C7047004C7047000000003D000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000003A4C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047000000 + 001600000046000000034C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047000000 + 00D8000000FF000000824C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047000000 + 00D7000000FF000000814C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047000000 + 001600000045000000034C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C704700000000174C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000063000000E20000 + 00144C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C704700000000194C70 + 47004C7047004C7047004C7047004C70470000000049000000FB000000FF0000 + 00B3000000014C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047000000004E000000F20000 + 003E4C7047004C7047004C7047000000002D000000F0000000FF000000FF0000 + 00FF000000744C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C70470000000025000000F0000000FF0000 + 00F40000003E4C7047000000001B000000E3000000FF000000FF000000FF0000 + 00FF000000FB0000003B4C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047000000000B000000D3000000FF000000FF0000 + 00FF000000F100000049000000D4000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000E1000000134C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C704700000000A1000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000B5000000014C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047000000000E000000550000005500000055000000550000 + 0055000000550000005500000055000000550000005500000055000000550000 + 0055000000550000005500000055000000124C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047000000003B000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000394C7047004C7047004C7047004C70470000000004000000C90000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00C7000000044C7047004C7047004C7047004C7047004C7047000000000C0000 + 0079000000A7000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000A7000000780000 + 000C4C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0015000000AC000000F4000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000F4000000AB000000144C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00AC000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000AA4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00F4000000FF000000E3000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000E3000000FF000000F34C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C70470000000071000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000714C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C704700000000AB000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000AB4C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C704700000000AB000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000AB4C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047000000003800000055000000550000 + 00550000005500000055000000554C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C704700000000AB000000FF000000FF0000 + 00FF000000FF000000FF000000FF4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C704700000000AB000000FF000000FF0000 + 00FF000000FF000000FF000000FF4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047000000003800000055000000550000 + 00550000005500000055000000554C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047000000001C000000550000 + 005500000055000000550000005500000055000000C7000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF0000007F4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF0000007F4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000FF000000FF000000FF000000FF0000007F4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000FF000000FF000000FF0000007F4C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000FF000000FF0000007F4C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00F4000000FF000000E3000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000C6000000FF0000 + 00FF000000FF000000804C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00AB000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000804C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0014000000AB000000F3000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00804C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C70470000000015000000AC000000F4000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000F4000000AC00000014000000AC000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000AB000000F4000000FF000000E2000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000C6000000FF0000 + 00FF000000C6000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000E2000000FF000000F3000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000550000000E00000055000000550000005500000055000000474C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C70470000000077000000FF000000FF000000F1000000384C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C70470000000077000000F1000000384C7047004C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047000000001C4C7047004C7047004C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000A9000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C70470000000055000000FF0000 + 00FF000000554C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000A9000000FF000000FF000000F4000000FF000000E2000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000AA000000AA000000AA000000AA000000AA000000C6000000FF0000 + 00FF000000C6000000AA000000AA000000AA000000AA000000AA000000AA0000 + 00AA000000E2000000FF000000F3000000AB000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000AA00000014000000AB000000F3000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000F3000000AA000000144C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000FF000000FF000000FF000000FF000000AB4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000AB000000FF000000FF000000FF000000FF000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000FF000000FF000000FF000000FF000000AB4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000AB000000FF000000FF000000FF000000FF000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000E3000000AA000000AA000000AA000000714C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 470000000071000000AA000000AA000000AA000000E3000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00AB000000AB000000714C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C70470000000071000000AB000000AB4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C704700000000300000002F4C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047000000002F000000FD000000FD0000002F4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047000000002F000000FD000000FD0000002E4C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047000000002F0000002F4C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00AB000000AB000000714C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C70470000000071000000AB000000AB4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000AA4C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C704700000000AA000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000E3000000AA000000AA000000AA000000714C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 470000000071000000AA000000AA000000AA000000E3000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000FF000000FF000000FF000000FF000000AB4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000AB000000FF000000FF000000FF000000FF000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00FF000000FF000000FF000000FF000000FF000000FF000000AB4C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000AB000000FF000000FF000000FF000000FF000000FF000000FF4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047000000000A0000006C0000009F0000008D000000474C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C704700000000120000003E0000005500000055000000550000 + 001F0000000D000000D7000000FF000000FF000000FF000000FF000000804C70 + 470000000009000000350000004B0000004A000000154C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 470000000021000000AC000000FA000000FF000000FF000000FF000000FF0000 + 003400000068000000FF000000FF000000FF000000FF000000FF000000F90000 + 009D000000F6000000FF000000FF000000FF000000FC00000094000000064C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0037000000EE000000FF000000FF000000FF000000FF000000FF000000FF0000 + 000F0000009C000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000AA4C70 + 47004C7047004C7047004C7047004C7047004C7047004C704700000000150000 + 00E6000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 0007000000A3000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 004D4C7047004C7047004C7047004C7047004C7047004C7047000000008D0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 001E00000089000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00B74C7047004C7047004C7047004C7047004C70470000000003000000ED0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00540000004B000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00F6000000054C7047004C7047004C7047004C7047000000002F000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00A200000007000000E8000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000002D4C7047004C7047004C7047004C7047000000004B000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00F80000001700000079000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000AD000000AD000000FF0000 + 00FF000000424C7047004C7047004C7047004C7047000000004B000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000009800000008000000DA000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000AD000000AD000000FF0000 + 00FF000000504C7047004C7047004C7047004C70470000000031000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FC0000004100000031000000F3000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000474C7047004C7047004C7047004C70470000000004000000EF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000E8000000270000003B000000E3000000FF000000FE0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000002E4C7047004C7047004C7047004C7047004C704700000000970000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000E70000004C0000000C0000006B000000FF0000 + 00B000000062000000CA000000FF000000FF000000FF000000FF000000FF0000 + 00FF0000000F4C7047004C7047004C7047004C7047004C704700000000300000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000EC000000F8000000DF0000 + 00094C7047000000000C000000DE000000FF000000FF000000FF000000FF0000 + 00EB4C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 00CC000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000794C70 + 47004C7047004C70470000000079000000FF000000FF000000FF000000FF0000 + 00B74C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0076000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000 + 00FF000000FF000000FF000000FF000000FF000000FF000000FF000000334C70 + 47004C7047004C7047000000002E000000FF000000FF000000FF000000FF0000 + 00754C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0035000000FF000000FF000000FF000000FF000000FF000000C7000000550000 + 0055000000C7000000FF000000FF000000FF000000FF000000FC000000074C70 + 47004C7047004C70470000000014000000FF000000FF000000FF000000FE0000 + 00254C7047004C7047004C7047004C7047004C7047004C7047004C7047000000 + 0005000000F8000000FF000000FF000000FF000000FF000000AA4C7047004C70 + 4700000000AA000000FF000000FF000000FF000000FF000000E04C7047004C70 + 47004C7047004C70470000000008000000FF000000FF000000FF000000B14C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000DD000000FF000000FF000000FF000000FF000000AA4C7047004C70 + 4700000000AA000000FF000000FF000000FF000000FF000000CD4C7047004C70 + 47004C7047000000000700000086000000FF000000FF000000ED0000001F4C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000C1000000FF000000FF000000FF000000FF000000AA4C7047004C70 + 4700000000AA000000FF000000FF000000FF000000FF000000BF4C7047004C70 + 47004C70470000000055000000FF000000FE000000B7000000244C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000B3000000FF000000FF000000FF000000FF000000AA4C7047004C70 + 4700000000AA000000FF000000FF000000FF000000FF000000B14C7047004C70 + 47004C7047000000001C0000004B0000001E4C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000B0000000FF000000FF000000FF000000FF000000AA4C7047004C70 + 4700000000AA000000FF000000FF000000FF000000FF000000A74C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 4700000000AC000000FF000000FF000000FF000000FF000000AA4C7047004C70 + 4700000000AA000000FF000000FF000000FF000000FF000000A94C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C7047004C7047004C7047004C7047004C7047004C70 + 47004C7047004C7047004C704700 + } + end + object ALWidgets: TActionList + Images = ILWidgets + left = 344 + top = 16 + object AGo: TAction + Caption = 'AGo' + ImageIndex = 0 + OnExecute = AGoExecute + OnUpdate = AGoUpdate + end + object AGoExternal: TAction + Caption = 'Go External' + Hint = 'Design using external browser' + ImageIndex = 1 + OnExecute = AGoExternalExecute + end + end + object tmrShowChromium: TTimer + Enabled = False + Interval = 300 + OnTimer = tmrShowChromiumTimer + left = 84 + top = 116 + end +end diff --git a/demo/webwidget/nativedesign/frmmain.pp b/demo/webwidget/nativedesign/frmmain.pp new file mode 100644 index 0000000..382d5f4 --- /dev/null +++ b/demo/webwidget/nativedesign/frmmain.pp @@ -0,0 +1,556 @@ +unit frmmain; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, webideintf, Forms, Controls, Graphics, Dialogs, EditBtn, ExtCtrls, ComCtrls, StdCtrls, ActnList, + {$IFDEF LINUX} + WebBrowserCtrls, WebBrowserIntf, + {$ELSE} + {$IFDEF WINDOWS} + Windows, Messages, uCEFChromium, uCEFWindowParent, uCEFChromiumWindow, uCEFTypes, uCEFInterfaces, uCEFWinControl, uCEFApplication, + {$ELSE} + {$ERROR Unsupported platform} + {$ENDIF} + {$ENDIF} fpJSON; + +type + + { TMainForm } + + TMainForm = class(TForm) + AGoExternal: TAction; + AGo: TAction; + ALWidgets: TActionList; + FEProject: TFileNameEdit; + ILWidgets: TImageList; + MLog: TMemo; + PCDesigner: TPageControl; + Project: TLabel; + PBottom: TPanel; + TBExternalGo: TToolButton; + tmrShowChromium: TTimer; + TSInspector: TTabSheet; + TSBrowser: TTabSheet; + TSLog: TTabSheet; + TBWidgets: TToolBar; + TBGo: TToolButton; + ToolButton1: TToolButton; + procedure AGoExecute(Sender: TObject); + procedure AGoExternalExecute(Sender: TObject); + procedure AGoUpdate(Sender: TObject); + procedure DEProjectEditingDone(Sender: TObject); + procedure FormCloseQuery(Sender: TObject; var CanClose: boolean); + procedure FormCreate(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure tmrShowChromiumTimer(Sender: TObject); + private + FClientID : Int64; // Just one for now + FDesignCaption : String; + FWebIDEIntf : TIDEServer; + FWidgetCount : Integer; + FWidgets : Array of String; + FURL : String; + FURLCount : Integer; + FCanClose : Boolean; + FAllowGo: Boolean; +{$IFDEF LINUX} + FWBDesign : TWebBrowser; + FWIDesign : TWebInspector; + FLastEmbeddedURI : String; + procedure wbDesignConsoleMessage(Sender: TObject; const Message, Source: string; Line: Integer); + procedure wbDesignError(Sender: TObject; const Uri: string; ErrorCode: LongWord; const ErrorMessage: string; var Handled: Boolean); + procedure wbDesignFavicon(Sender: TObject); + procedure wbDesignHitTest(Sender: TObject; X, Y: Integer; HitTest: TWebHitTest; const Link, Media: string); + procedure wbDesignLoadStatusChange(Sender: TObject); + procedure wbDesignLocationChange(Sender: TObject); + procedure wbDesignNavigate(Sender: TObject; const Uri: string; var aAction: TWebNavigateAction); + procedure wbDesignProgress(Sender: TObject; Progress: Integer); + procedure wbDesignRequest(Sender: TObject; var Uri: string); + procedure wbDesignScriptDialog(Sender: TObject; Dialog: TWebScriptDialog; const Message: string; var Input: string; var Accepted: Boolean; var Handled: Boolean); +{$ENDIF} +{$IFDEF WINDOWS} + FClosing : Boolean; + cwDesign : TChromiumWindow; + procedure WMMove(var aMessage : TWMMove); message WM_MOVE; + procedure WMMoving(var aMessage : TMessage); message WM_MOVING; + // You also have to handle these two messages to set GlobalCEFApp.OsmodalLoop + procedure WMEnterMenuLoop(var aMessage: TMessage); message WM_ENTERMENULOOP; + procedure WMExitMenuLoop(var aMessage: TMessage); message WM_EXITMENULOOP; + procedure cwBeforeClose(Sender: TObject); + procedure cwClose(Sender: TObject); + procedure cwAfterCreated(Sender: TObject); + procedure cwOnBeforePopup(Sender: TObject; + const browser: ICefBrowser; const frame: ICefFrame; const targetUrl, + targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition; + userGesture: Boolean; const popupFeatures: TCefPopupFeatures; + var windowInfo: TCefWindowInfo; var client: ICefClient; + var settings: TCefBrowserSettings; + var extra_info: ICefDictionaryValue; + var noJavascriptAccess: Boolean; + var Result: Boolean); +{$ENDIF} + function GetProjectURL: String; + procedure DoAddWidget(Sender: TObject); + procedure DoAction(Sender: TObject; aExchange: TIDEExchange); + procedure DoClientCame(Sender: TObject; aClient: TIDEClient); + procedure DoClientLeft(Sender: TObject; aClient: TIDEClient); + procedure DoLogRequest(Sender: TObject; aURL: String); + procedure IsWidgetEnabled(Sender: TObject); + procedure LogRequest; + Procedure RegisterWidgets; + Procedure RegisterWidget(aWidget: String; aImageIndex : Integer); + procedure SetUpEmbeddedBrowser; + public + Procedure Log(Msg : String); + Procedure Log(Fmt : String; Args : Array of const); + end; + +var + MainForm: TMainForm; + +implementation + +uses lclintf, fpmimetypes; + +{$R *.lfm} + +{ TMainForm } + +procedure TMainForm.DEProjectEditingDone(Sender: TObject); +begin + FWebIDEIntf.ProjectDir:=ExtractFilePath(FEProject.FileName); +end; + +procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: boolean); +begin + FWebIDEIntf.Active:=False; + CanClose:=FCanClose; +{$IFDEF WINDOWS} + if not(FClosing) then + begin + FClosing := True; + Visible := False; + cwDesign.CloseBrowser(True); + end; +{$ENDIF} +end; + +Function TMainForm.GetProjectURL : String; + +begin + Result:=Format('http://localhost:%d/Project/%s',[FWebIDEIntf.Port,ExtractFileName(FEProject.FileName)]); +end; + +procedure TMainForm.AGoExecute(Sender: TObject); + +Var + URL : String; + +begin + URL:=GetProjectURL; + Log('Going to URL: %s',[URL]); +{$IFDEF LINUX} + FWBDesign.Location:=URL; +{$ENDIF} +{$IFDEF WINDOWS} + cwDesign.LoadURL(URL); +{$ENDIF} +end; + +procedure TMainForm.AGoExternalExecute(Sender: TObject); +Var + URL : String; + +begin + URL:=GetProjectURL; + Log('Going to URL: %s',[URL]); + OpenURL(URL); +end; + +procedure TMainForm.AGoUpdate(Sender: TObject); +begin + (Sender as Taction).Enabled:=FAllowGo; +end; + +procedure TMainForm.FormCreate(Sender: TObject); + + + +begin + FAllowGo:=False; + FDesignCaption:=Caption; +{$IFDEF Linux} + MimeTypes.LoadFromFile('/etc/mime.types'); +{$ENDIF} +{$IFDEF WINDOWS} + MimeTypes.LoadFromFile(ExtractFilePath(Paramstr(0))+'mime.types'); +{$ENDIF} + FEProject.FileName:=ExtractFilePath(Paramstr(0))+'designdemo'+PathDelim+'designdemo.html'; + FWebIDEIntf:=TIDEServer.Create(Self); + FWebIDEIntf.ProjectDir:=ExtractFilePath(FEProject.FileName); + FWebIDEIntf.OnClientAdded:=@DoClientCame; + FWebIDEIntf.OnClientRemoved:=@DoClientLeft; + FWebIDEIntf.OnRequest:=@DoLogRequest; + FWebIDEIntf.OnAction:=@DoAction; + FWebIDEIntf.Active:=True; + RegisterWidgets; + SetUpEmbeddedBrowser; +end; + +procedure TMainForm.FormShow(Sender: TObject); +begin +{$IFDEF WINDOWS} + with cwDesign do + begin + ChromiumBrowser.OnBeforePopup := @cwOnBeforePopup; + if not CreateBrowser then + tmrShowChromium.Enabled := True; + end; +{$ENDIF} +end; + +procedure TMainForm.tmrShowChromiumTimer(Sender: TObject); +begin + tmrShowChromium.Enabled := False; +{$IFDEF WINDOWS} + With cwDesign do + if not (CreateBrowser or Initialized) then + tmrShowChromium.Enabled := True; +{$ENDIF} +end; + +{$IFDEF WINDOWS} +procedure TMainForm.SetUpEmbeddedBrowser; + +begin + FCanClose:=False; + cwDesign:=TChromiumWindow.Create(Self); + With cwDesign do + begin + Parent:=TSBrowser; + Align:=alClient; + OnClose:=@cwClose; + OnBeforeClose:=@cwBeforeClose; + OnAfterCreated:=@cwAfterCreated; + end; + TSInspector.TabVisible:=False; +end; + +procedure TMainForm.WMMove(var aMessage : TWMMove); + +begin + inherited; + if (cwDesign <> nil) then + cwDesign.NotifyMoveOrResizeStarted; +end; + +procedure TMainForm.WMMoving(var aMessage : TMessage); + +begin + inherited; + if (cwDesign <> nil) then + cwDesign.NotifyMoveOrResizeStarted; +end; + +procedure TMainForm.WMEnterMenuLoop(var aMessage: TMessage); + +begin + inherited; + if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then + GlobalCEFApp.OsmodalLoop := True; +end; + +procedure TMainForm.WMExitMenuLoop(var aMessage: TMessage); + +begin + inherited; + if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then + GlobalCEFApp.OsmodalLoop := False; +end; + +procedure TMainForm.cwBeforeClose(Sender: TObject); +begin + FCanClose := True; + PostMessage(Handle, WM_CLOSE, 0, 0); +end; + + +procedure TMainForm.cwClose(Sender: TObject); +begin + // DestroyChildWindow will destroy the child window created by CEF at the top of the Z order. + if not(cwDesign.DestroyChildWindow) then + begin + FCanClose := True; + Close; + end; +end; + +procedure TMainForm.cwOnBeforePopup(Sender: TObject; + const browser: ICefBrowser; const frame: ICefFrame; const targetUrl, + targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition; + userGesture: Boolean; const popupFeatures: TCefPopupFeatures; + var windowInfo: TCefWindowInfo; var client: ICefClient; + var settings: TCefBrowserSettings; + var extra_info: ICefDictionaryValue; + var noJavascriptAccess: Boolean; + var Result: Boolean); +begin + // For simplicity, this demo blocks all popup windows and new tabs + Result := (targetDisposition in [WOD_NEW_FOREGROUND_TAB, WOD_NEW_BACKGROUND_TAB, WOD_NEW_POPUP, WOD_NEW_WINDOW]); +end; + +procedure TMainForm.cwAfterCreated(Sender: TObject); +begin + // Now the browser is fully initialized we can load the initial web page. + FAllowGo:=True; +end; + +{$ENDIF} + +{$IFDEF LINUX} +procedure TMainForm.SetUpEmbeddedBrowser; + +begin + FAllowGo:=True; + FCanClose:=True; + FWBDesign:=TWebBrowser.Create(Self); + With FWBDesign do + begin + Parent:=TSBrowser; + Align:=alClient; + DesignMode := False; + SourceView := False; + ZoomContent := False; + ZoomFactor := 1; + { lots of logging } + OnConsoleMessage:=@wbDesignConsoleMessage; + OnScriptDialog:=@wbDesignScriptDialog; + OnError:=@wbDesignError; + OnFavicon:=@wbDesignFavicon; + OnHitTest:=@wbDesignHitTest; + OnLoadStatusChange:=@wbDesignLoadStatusChange; + OnLocationChange:=@wbDesignLocationChange; + OnNavigate:=@wbDesignNavigate; + OnProgress:=@wbDesignProgress; + OnRequest:=@wbDesignRequest; + end; + FWIDesign:=TWebInspector.Create(Self); + With FWIDesign do + begin + Parent:=TSInspector; + Align:=alClient; + Active:=True; + WebBrowser:=FWBDesign; + end; + TSInspector.TabVisible:=true; + PCDesigner.ActivePage:=TSBrowser; +end; + +procedure TMainForm.wbDesignConsoleMessage(Sender: TObject; const Message, Source: string; Line: Integer); +begin + Log('Console message: %s (%s: %d)',[Message,Source,Line]); +end; + + +procedure TMainForm.wbDesignError(Sender: TObject; const Uri: string; ErrorCode: LongWord; const ErrorMessage: string; + var Handled: Boolean); +begin + Log('Error: %s, code: %d, Message: %s',[URI,ErrorCode,ErrorMessage]); + Handled:=True; +end; + +procedure TMainForm.wbDesignFavicon(Sender: TObject); +begin + Log('Favicon available/missed'); +end; + +procedure TMainForm.wbDesignHitTest(Sender: TObject; X, Y: Integer; HitTest: TWebHitTest; const Link, Media: string); +begin +// Log('Hit test (%d,%d) link: %s, media: %s',[x,y,link,media]); +end; + +procedure TMainForm.wbDesignLoadStatusChange(Sender: TObject); +begin + Log('Load status change'); +end; + +procedure TMainForm.wbDesignLocationChange(Sender: TObject); +begin + Log('Location change'); +end; + +procedure TMainForm.wbDesignNavigate(Sender: TObject; const Uri: string; var aAction: TWebNavigateAction); +begin + Log('Navigation: %s',[URI]); + aAction:=naAllow; +end; + +procedure TMainForm.wbDesignProgress(Sender: TObject; Progress: Integer); +begin + Log('Progress: %d',[Progress]) +end; + +procedure TMainForm.wbDesignRequest(Sender: TObject; var Uri: string); +begin + if Uri<>FLastEmbeddedURI then + Log('Embedded browser doing request : %s',[URI]); + FLastEmbeddedURI:=URI; +end; + +procedure TMainForm.wbDesignScriptDialog(Sender: TObject; Dialog: TWebScriptDialog; const Message: string; var Input: string; + var Accepted: Boolean; var Handled: Boolean); +begin + Log('Script dialog Message: %s; Input : %s',[message,Input]); + Accepted:=true; + Handled:=true; +end; +{$ENDIF} + +procedure TMainForm.DoAction(Sender: TObject; aExchange: TIDEExchange); + +var + PayJSON : TJSONObject; + +begin + payJSON:=Nil; + if Not (aExchange.Payload is TJSONObject) then + begin + Log('Payload is not JSON Object'); + exit; + end; + payJSON:=aExchange.Payload as TJSONObject; + with aExchange do + case Name of + 'create': + Log('Browser created widget of class %s, name %s',[PayJSON.Get('class',''),PayJSON.Get('widget','')]); + 'select': + begin + Log('Browser selected widget of class %s, name %s',[PayJSON.Get('class',''),PayJSON.Get('widget','')]); + Log('Selected widget state: '+PayJSON.Get('state','')); + end; + end; +end; + +procedure TMainForm.DoClientCame(Sender: TObject; aClient: TIDEClient); +begin + if FClientID>0 then + Log('Ignoring second client (id: %d) attachment.',[aClient.ID]) + else + begin + FClientID:=aClient.ID; + Caption:=FDesignCaption+Format(' [Client: %d]',[FClientID]); + end; +end; + +procedure TMainForm.DoAddWidget(Sender: TObject); + +Var + Cmd : TIDECommand; + aName : String; + +begin + aName:=FWidgets[(Sender as TAction).Tag]; + Cmd:=TIDECommand.Create; + Cmd.NeedsConfirmation:=True; + Cmd.ClientID:=FClientID; + Cmd.name:='addWidget'; + Cmd.PayLoad:=TJSONObject.Create(['class','T'+aName+'Widget']); + FWebIDEIntf.SendCommand(cmd); +end; + +procedure TMainForm.DoClientLeft(Sender: TObject; aClient: TIDEClient); +begin + if (aClient.ID=FClientID) then + begin + FClientID:=-1; + Caption:=FDesignCaption; + end; +end; + +procedure TMainForm.LogRequest; + +begin + if (FURLCount=1) then // avoid excessive logging, command loop is on very short interval. + Log('Internal server request received: '+FURL); +end; + +procedure TMainForm.DoLogRequest(Sender: TObject; aURL: String); +begin + if (aURL<>FURL) then + begin + FURLCount:=1; + FURL:=aURL + end + else + Inc(FURLCount); + TThread.Synchronize(TThread.CurrentThread,@LogRequest); +end; + +procedure TMainForm.IsWidgetEnabled(Sender: TObject); +begin + (Sender as TAction).Enabled:=(FClientID<>-1); +end; + +procedure TMainForm.RegisterWidgets; +begin + SetLength(FWidgets,9); + FWidgetCount:=0; + RegisterWidget('Button',2); + RegisterWidget('Checkbox',3); + RegisterWidget('Radio',4); + RegisterWidget('Edit',5); + RegisterWidget('Image',6); + RegisterWidget('TextArea',7); + RegisterWidget('Select',8); + RegisterWidget('Container',9); + RegisterWidget('Jumbo',10); +end; + +procedure TMainForm.RegisterWidget(aWidget: String; aImageIndex: Integer); + +Var + A : TAction; + B : TToolButton; + L,i : Integer; + +begin + FWidgets[FWidgetCount]:=aWidget; + A:=TAction.Create(Self); + A.ActionList:=ALWidgets; + A.Name:='AAdd'+aWidget; + A.Hint:='Add '+aWidget; + A.Caption:='Add '+aWidget; + A.ImageIndex:=aImageIndex; + A.Tag:=FWidgetCount; + A.OnExecute:=@DoAddWidget; + A.OnUpdate:=@IsWidgetEnabled; + L:=0; + For I:=0 to TBWidgets.ControlCount-1 do + if TBWidgets.Controls[i].BoundsRect.Right>L then + L:=TBWidgets.Controls[i].BoundsRect.Right; + B:=TToolButton.Create(Self); + B.Parent:=TBWidgets; + B.Left:=L; + B.Height:=32; + B.Action:=A; + inc(FWidgetCount); +// TBWidgets.AddControl;; + +end; + +procedure TMainForm.Log(Msg: String); +begin + MLog.Lines.Add(Msg); +end; + +procedure TMainForm.Log(Fmt: String; Args: array of const); +begin + Log(Format(Fmt,Args)); +end; + + +end. + diff --git a/demo/webwidget/nativedesign/nativedesigner.ico b/demo/webwidget/nativedesign/nativedesigner.ico new file mode 100644 index 0000000..25c186a Binary files /dev/null and b/demo/webwidget/nativedesign/nativedesigner.ico differ diff --git a/demo/webwidget/nativedesign/nativedesigner.lpi b/demo/webwidget/nativedesign/nativedesigner.lpi new file mode 100644 index 0000000..215dd70 --- /dev/null +++ b/demo/webwidget/nativedesign/nativedesigner.lpi @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectOptions> + <Version Value="12"/> + <General> + <Flags> + <SaveOnlyProjectUnits Value="True"/> + <SaveJumpHistory Value="False"/> + <SaveFoldState Value="False"/> + </Flags> + <SessionStorage Value="InProjectDir"/> + <Title Value="nativedesigner"/> + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + <Icon Value="0"/> + </General> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + <Modes Count="0"/> + </RunParams> + <RequiredPackages Count="3"> + <Item1> + <PackageName Value="weblaz"/> + </Item1> + <Item2> + <PackageName Value="WebBrowser"/> + </Item2> + <Item3> + <PackageName Value="LCL"/> + </Item3> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="nativedesigner.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="frmmain.pp"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit> + <Unit> + <Filename Value="webideintf.pp"/> + <IsPartOfProject Value="True"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="nativedesigner"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/demo/webwidget/nativedesign/nativedesigner.lpr b/demo/webwidget/nativedesign/nativedesigner.lpr new file mode 100644 index 0000000..ee50568 --- /dev/null +++ b/demo/webwidget/nativedesign/nativedesigner.lpr @@ -0,0 +1,22 @@ +program nativedesigner; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX} + cthreads, + {$ENDIF} + Interfaces, // this includes the LCL widgetset + Forms, frmmain, webideintf + { you can add units after this }; + +{$R *.res} + +begin + RequireDerivedFormResource:=True; + Application.Scaled:=True; + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. + diff --git a/demo/webwidget/nativedesign/nativedesigner.res b/demo/webwidget/nativedesign/nativedesigner.res new file mode 100644 index 0000000..0ce2e2b Binary files /dev/null and b/demo/webwidget/nativedesign/nativedesigner.res differ diff --git a/demo/webwidget/nativedesign/webideintf.pp b/demo/webwidget/nativedesign/webideintf.pp new file mode 100644 index 0000000..ddab347 --- /dev/null +++ b/demo/webwidget/nativedesign/webideintf.pp @@ -0,0 +1,817 @@ +unit webideintf; + +{$mode objfpc}{$H+} + +interface + +uses + fpMimeTypes, Classes, SysUtils, StrUtils, httpdefs, fphttpclient,custhttpapp, fpjson, jsonparser, httproute; + +Const + SFilesURL = '/Project/'; + SIDEURL = '/IDE/'; + +Type + TClientObject = Class(TObject) + Private + FID: Int64; + public + Procedure FromJSON(aJSON : TJSONObject); virtual; abstract; + Procedure ToJSON(aJSON : TJSONObject); virtual; abstract; + Property ID : Int64 Read FID Write FID; + end; + { TIDEClient } + + TIDEClient = Class(TClientObject) + private + FURL: String; + Public + Procedure FromJSON(aJSON : TJSONObject); override; + Procedure ToJSON(aJSON : TJSONObject); override; + Property URL : String Read FURL Write FURL; + end; + { TIDEExchange } + + TIDEExchange = Class(TClientObject) + private + FClientID: Int64; + FName: String; + FPayLoad: TJSONData; + Public + Destructor Destroy; override; + Procedure FromJSON(aJSON : TJSONObject); override; + Procedure ToJSON(aJSON : TJSONObject); override; + Property ClientID : Int64 Read FClientID Write FClientID; + Property Name : String Read FName Write FName; + Property PayLoad : TJSONData Read FPayLoad Write FPayLoad; + end; + + TIDEAction = Class(TIDEExchange) + end; + + { TClientObjectList } + + TClientObjectList = Class(TThreadList) + Public + Function FindID(aID : int64) : TClientObject; + end; + + { TIDECommand } + + TIDECommand = Class(TIDEExchange) + private + FConfirmed: Boolean; + FNeedsConfirmation: Boolean; + FSent: Boolean; + Public + Property NeedsConfirmation : Boolean Read FNeedsConfirmation Write FNeedsConfirmation; + Property Sent : Boolean Read FSent Write FSent; + Property Confirmed : Boolean Read FConfirmed Write FConfirmed; + end; + + { TIDEThread } + + TIDEThread = Class(TThread) + Private + FHandler : TFPHTTPServerHandler; + FExceptionClass : String; + FExceptionMessage : String; + Public + Constructor Create(aHandler : TFPHTTPServerHandler); + Procedure Execute; override; + end; + + + TIDENotification = Procedure(Sender : TObject; aExchange : TIDEExchange) of object; + TIDEClientNotification = Procedure(Sender : TObject; aClient : TIDEClient) of object; + TIDERequestNotification = Procedure(Sender : TObject; aURL : String) of object; + + { TIDEServer } + + TIDEServer = Class(TComponent) + private + FOnRequest: TIDERequestNotification; + FQuitting : Boolean; + FClients, + FCommands, + FActions : TClientObjectList; + FIDCounter: Int64; + FOnAction: TIDENotification; + FOnClient: TIDEClientNotification; + FOnClientRemoved: TIDEClientNotification; + FOnConfirmCommand: TIDENotification; + FProjectDir: String; + FWebHandler : TFPHTTPServerHandler; + FThread : TIDEThread; + FLastAction : TIDEAction; + FLastCommand : TIDECommand; + FLastClient : TIDEClient; + function CheckClient(aRequest: TRequest): INt64; + procedure DeActivatedThread(Sender: TObject); + function Do404(is404: boolean; aResponse: TResponse): Boolean; + procedure DoEvent(aProc: TThreadMethod); + procedure DoQuit(ARequest: TRequest; AResponse: TResponse); + procedure DoRouteRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse); + function GetAction(Index : Integer): TIDEAction; + function GetActionCount: Integer; + function GetPort: Integer; + function GetActive: Boolean; + procedure SetActive(AValue: Boolean); + procedure SetPort(AValue: Integer); + procedure SetProjectDir(AValue: String); + Protected + procedure RegisterRoutes; virtual; + // HTTP request extraction + procedure GetClientObjectFromRequest(ARequest: TRequest; AObject: TClientObject); + function GetActionFromRequest(ARequest: TRequest): TIDEAction; + function GetCommandFromRequest(ARequest: TRequest): TIDECommand; + function GetClientFromRequest(ARequest: TRequest): TIDEClient; + function GetJSONFromRequest(ARequest: TRequest): TJSONObject; + // Sending responses + procedure SendClientObjectResponse(AObject: TClientObject; AResponse: TResponse); + Procedure SendJSONResponse(aJSON : TJSONObject; aResponse : TResponse); + // HTTP route handlers + procedure DoDeleteAction(ARequest: TRequest; AResponse: TResponse); virtual; + procedure DoDeleteClient(ARequest: TRequest; AResponse: TResponse); virtual; + procedure DoGetCommand(ARequest: TRequest; AResponse: TResponse);virtual; + procedure DoGetFile(ARequest: TRequest; AResponse: TResponse);virtual; + procedure DoPostAction(ARequest: TRequest; AResponse: TResponse);virtual; + procedure DoPostClient(ARequest: TRequest; AResponse: TResponse);virtual; + procedure DoPutCommand(ARequest: TRequest; AResponse: TResponse);virtual; + // Event handler synchronisation. Rework this to objects + Procedure DoOnAction; + Procedure DoOnConfirmCommand; + Procedure DoOnClientAdded; + Procedure DoOnClientRemoved; + Public + Constructor Create(aOwner : TComponent); override; + Destructor Destroy; override; + Function GetNextCounter : Int64; + // Public API to communicate with browser + Function SendCommand(aCommand : TIDECommand) : Int64; + Procedure GetClientActions(aClientID : Int64; aList : TFPList); + Function DeleteAction(aID: Int64; Const aClientID : Int64 = -1): Boolean; + // Public properties + Property ProjectDir : String Read FProjectDir Write SetProjectDir; + Property Port : Integer Read GetPort Write SetPort; + Property Active : Boolean read GetActive write SetActive; + Property ActionCount : Integer Read GetActionCount; + Property Action[Index : Integer] : TIDEAction Read GetAction; + // Events + Property OnRequest : TIDERequestNotification Read FOnRequest Write FOnRequest; + Property OnConfirmCommand : TIDENotification Read FOnConfirmCommand Write FOnConfirmCommand; + Property OnAction : TIDENotification Read FOnAction Write FOnAction; + Property OnClientAdded : TIDEClientNotification Read FOnClient Write FOnClient; + Property OnClientRemoved : TIDEClientNotification Read FOnClientRemoved Write FOnClientRemoved; + end; + +implementation + +{ TClientObjectList } + + +function TClientObjectList.FindID(aID: int64): TClientObject; + +Var + L : TList; + I : integer; + +begin + Result:=Nil; + L:=LockList; + try + I:=L.Count-1; + While (Result=Nil) and (I>=0) do + begin + Result:=TClientObject(L[i]); + if Result.ID<>aID then + Result:=nil; + Dec(I); + end; + finally + UnlockList; + end; +end; + +{ TIDEClient } + +procedure TIDEClient.FromJSON(aJSON: TJSONObject); +begin + FID:=aJSON.Get('id',Int64(-1)); + FURL:=aJSON.Get('url',''); +end; + +procedure TIDEClient.ToJSON(aJSON: TJSONObject); +begin + aJSON.Add('id',ID); + aJSON.Add('url',url); +end; + +{ TIDEExchange } + +destructor TIDEExchange.Destroy; +begin + FreeAndNil(FPayload); + Inherited; +end; + +procedure TIDEExchange.FromJSON(aJSON: TJSONObject); + +Var + P : TJSONObject; + +begin + ID:=aJSON.Get('id',Int64(0)); + Name:=aJSON.Get('name',''); + P:=aJSON.Get('payload',TJSONObject(Nil)); + if Assigned(P) then + Payload:=aJSON.Extract('payload'); +end; + +procedure TIDEExchange.ToJSON(aJSON: TJSONObject); +begin + aJSON.Add('id',ID); + aJSON.Add('name',name); + if Assigned(Payload) then + aJSON.Add('payload',Payload.Clone); +end; + +{ TIDEThread } + + +constructor TIDEThread.Create(aHandler: TFPHTTPServerHandler); +begin + FHandler:=AHandler; + FreeOnTerminate:=True; + Inherited Create(False); +end; + +procedure TIDEThread.Execute; +begin + try + FHandler.Run; + FHandler:=nil; + except + On E : Exception do + begin + FExceptionClass:=E.ClassName; + FExceptionMessage:=E.Message; + end; + end; +end; + +{ TIDEServer } + +function TIDEServer.GetAction(Index : Integer): TIDEAction; + +Var + L : TList; + +begin + L:=FActions.LockList; + try + Result:=TIDEAction(L.Items[Index]); + finally + FActions.UnlockList; + end; +end; + +procedure TIDEServer.DeActivatedThread(Sender: TObject); +begin + FThread:=Nil; +end; + +function TIDEServer.GetActionCount: Integer; + +Var + L : TList; + +begin + L:=FActions.LockList; + try + Result:=L.Count; + finally + FActions.UnlockList; + end; +end; + +function TIDEServer.GetActive: Boolean; +begin + Result:=Assigned(FThread); +end; + +function TIDEServer.GetPort: Integer; +begin + Result:=FWebHandler.Port; +end; + +procedure TIDEServer.SetActive(AValue: Boolean); +begin + if Active=AValue then Exit; + if AValue then + begin + FThread:=TIDEThread.Create(FWebHandler); + FThread.OnTerminate:=@DeActivatedThread; + end + else + begin + FWebHandler.Terminate; // will cause thread to stop. + try + // Send a Quit request just in case. Normally this should fail. + FQuitting:=True; + TFPHTTPClient.SimpleGet(Format('http://localhost:%d/Quit',[Port])); + except + FQuitting:=False; + end; + end; +end; + +procedure TIDEServer.SetPort(AValue: Integer); +begin + FWebHandler.Port:=aValue; +end; + +procedure TIDEServer.SetProjectDir(AValue: String); +begin + if FProjectDir=AValue then Exit; + FProjectDir:=IncludeTrailingPathDelimiter(AValue); +end; + +procedure TIDEServer.DoOnAction; +begin + If Assigned(FOnAction) then + FonAction(Self,FLastAction); + FLastAction:=Nil; +end; + +procedure TIDEServer.DoOnConfirmCommand; +begin + If Assigned(FOnAction) then + FonAction(Self,FLastCommand); + FLastCommand:=Nil; +end; + +procedure TIDEServer.DoOnClientAdded; +begin + if Assigned(FOnClient) then + FOnClient(Self,FLastClient); + FLastClient:=Nil; +end; + +procedure TIDEServer.DoOnClientRemoved; +begin + if Assigned(FOnClientRemoved) then + FOnClientRemoved(Self,FLastClient); + FLastClient:=Nil; +end; + +procedure TIDEServer.DoGetCommand(ARequest: TRequest; AResponse: TResponse); + +Var + L : TList; + I : integer; + J,C : TJSONObject; + A :TJSONArray; + Cmd : TIDECommand; + L2 : TFPList; + aClient : Int64; + +begin + aClient:=CheckClient(aRequest); + J:=nil; + A:=nil; + L:=FCommands.LockList; + try + L2:=TFPList.Create; + J:=TJSONObject.Create; + A:=TJSONArray.Create; + J.Add('commands',A); + For I:=0 to L.Count-1 do + begin + CMD:=TIDECommand(L[i]); + if Not Cmd.Sent and (Cmd.ClientID=aClient) then + begin + C:=TJSONObject.Create; + Cmd.ToJSON(C); + A.Add(C); + L2.Add(C); + end; + end; + SendJSONResponse(J,aResponse); + // Remove sent from list + for I:=0 to L2.Count-1 do + begin + Cmd:=TIDECommand(L[i]); + if Cmd.NeedsConfirmation then + Cmd.Sent:=True + else + begin + Cmd.Free; + L.Remove(Cmd); + end; + end; + finally + J.Free; + FCommands.UnLockList; + l2.Free; + end; +end; + +procedure TIDEServer.DoPutCommand(ARequest: TRequest; AResponse: TResponse); + +Var + cmd,oCmd : TIDECommand; + aID,aClient : Int64; + +begin + aClient:=CheckClient(aRequest); + aID:=StrToIntDef(aRequest.RouteParams['ID'],-1); + cmd:=TIDECommand.Create; + try + GetClientObjectFromRequest(aRequest,Cmd); + cmd.ClientID:=aClient; + oCmd:=TIDECommand(FCommands.FindID(aID)); + if Do404((oCmd=Nil) or (oCmd.ClientID<>aClient),aResponse) then + exit; + // Later on we can add more modifications + oCmd.Confirmed:=True; + aResponse.Code:=204; + aResponse.CodeText:='OK'; + aResponse.SendResponse; + FLastCommand:=oCmd; + DoEvent(@DoOnConfirmCommand); + FCommands.Remove(oCmd); + Finally + cmd.Free; + end; +end; + +procedure TIDEServer.DoQuit(ARequest: TRequest; AResponse: TResponse); +begin + if FQuitting then + aResponse.Code:=200 + else + aResponse.Code:=401; + aResponse.SendResponse; +end; + +procedure TIDEServer.DoRouteRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse); +begin + If Assigned(FonRequest) then + FOnRequest(Self,aRequest.URI); +end; + +function TIDEServer.GetJSONFromRequest(ARequest: TRequest): TJSONObject; + +var + D : TJSONData; + +begin + if ARequest.ContentType<>'application/json' then + Raise Exception.Create('Not valid JSON payload: content type must be application/json'); + D:=GetJSON(ARequest.Content); + if Not (D is TJSONObject) then + begin + FreeAndNil(D); + Raise EJSON.Create('Payload is valid JSON but not a JSON object'); + end; + Result:=D as TJSONObject; +end; + +procedure TIDEServer.SendJSONResponse(aJSON: TJSONObject; aResponse: TResponse); + +Var + JS : TJSONStringType; + +begin + JS:=aJSON.AsJSON; + aResponse.FreeContentStream:=True; + aResponse.ContentStream:=TMemoryStream.Create; + aResponse.ContentStream.WriteBuffer(JS[1],Length(JS)); + aResponse.ContentLength:=Length(JS); + aResponse.ContentType:='application/json'; + aResponse.SendResponse; +end; + +procedure TIDEServer.GetClientObjectFromRequest(ARequest: TRequest; AObject: TClientObject); + +Var + J : TJSONObject; + +begin + J:=GetJSONFromRequest(aRequest); + try + AObject.FromJSON(J); + finally + J.Free; + end; +end; + +procedure TIDEServer.SendClientObjectResponse(AObject: TClientObject; AResponse: TResponse); + +Var + J : TJSONObject; + +begin + J:=TJSONObject.Create; + try + aObject.ToJSON(J); + SendJSONResponse(J,aResponse); + finally + J.Free; + end; +end; + +function TIDEServer.GetActionFromRequest(ARequest: TRequest): TIDEAction; + +begin + Result:=TIDEAction.Create; + try + GetClientObjectFromRequest(aRequest,Result); + except + Result.Free; + raise; + end; +end; + +function TIDEServer.GetCommandFromRequest(ARequest: TRequest): TIDECommand; + +begin + Result:=TIDECommand.Create; + try + GetClientObjectFromRequest(aRequest,Result); + except + Result.Free; + Raise; + end; +end; + +function TIDEServer.GetClientFromRequest(ARequest: TRequest): TIDEClient; +begin + Result:=TIDEClient.Create; + try + GetClientObjectFromRequest(aRequest,Result); + except + Result.Free; + Raise; + end; +end; + +procedure TIDEServer.DoPostAction(ARequest: TRequest; AResponse: TResponse); + +var + A : TIDEAction; + aId,aClient : Int64; + +begin + aClient:=CheckClient(aRequest); + aID:=StrToInt64Def(aRequest.RouteParams['ID'],-1); + Try + A:=GetACtionFromRequest(aRequest); + A.ClientID:=aClient; + if A.ID=0 then + a.ID:=aID; + FActions.Add(A); + FLastAction:=A; + DoEvent(@DoOnAction); + AResponse.Code:=201; + AResponse.Codetext:='Created'; + except + On E: Exception do + begin + AResponse.Code:=400; + AResponse.Codetext:='Invalid Param'; + AResponse.Content:='Invalid data ('+E.ClassName+'): '+E.Message; + end; + end; + aResponse.SendResponse; +end; + +function TIDEServer.CheckClient(aRequest: TRequest): INt64; + +Var + S : String; + +begin + S:=ARequest.RouteParams['Client']; + if (S='') then + Raise EJSON.Create('Missing client ID in request'); + if Not TryStrToInt64(S,Result) then + Raise EJSON.CreateFmt('Invalid client ID: %s',[S]); +end; + +procedure TIDEServer.DoDeleteAction(ARequest: TRequest; AResponse: TResponse); + +var + SID : String; + ID,aClient : Int64; + +begin + Try + aClient:=CheckClient(ARequest); + SID:=ARequest.RouteParams['ID']; + ID:=StrtoInt64Def(SID,-1); + if Do404((ID=-1) or not (DeleteAction(ID,aClient)),aResponse) then + exit; + AResponse.Code:=204; + AResponse.Codetext:='No content'; + aResponse.SendResponse; + except + On E: Exception do + begin + AResponse.Code:=400; + AResponse.Codetext:='Invalid Param'; + AResponse.Content:='Invalid data ('+E.ClassName+'): '+E.Message; + end; + end; +end; + + +procedure TIDEServer.DoGetFile(ARequest: TRequest; AResponse: TResponse); + +Var + FN : String; + +begin + FN:=ARequest.URL; + if AnsiStartsText(SFilesURL,FN) then + Delete(FN,1,Length(SFilesURL)); + FN:=ExpandFileName(FProjectDir+FN); + if Pos('..',ExtractRelativepath(FProjectDir,FN))<>0 then + begin + aResponse.Code:=401; + aResponse.CodeText:='Forbidden'; + aResponse.Content:='<H1>Forbidden</H1>'; + end + else if Do404(Not FileExists(FN),aResponse) then + exit; + aResponse.FreeContentStream:=True; + aResponse.ContentStream:=TFileStream.Create(FN,fmOpenRead or fmShareDenyWrite); + aResponse.ContentLength:=aResponse.ContentStream.Size; + aResponse.ContentType:=MimeTypes.GetMimeType(ExtractFileExt(FN)); + if aResponse.ContentType='' then + aResponse.ContentType:='text/html'; + aResponse.SendResponse; +end; + + +constructor TIDEServer.Create(aOwner: TComponent); +begin + Inherited; + FProjectDir:=ExtractFilePath(Paramstr(0)); + FActions:=TClientObjectList.Create; + FCommands:=TClientObjectList.Create; + FClients:=TClientObjectList.Create; + FWebHandler:=TFPHTTPServerHandler.Create(Self); + FWebHandler.Port:=8080; + RegisterRoutes; +end; + +procedure TIDEServer.DoEvent(aProc : TThreadMethod); + +begin + if Assigned(FThread) then + FThread.Synchronize(aProc) + else + aProc; +end; + +procedure TIDEServer.DoPostClient(ARequest: TRequest; AResponse: TResponse); + +Var + aClient : TIDEClient; + +begin + aClient:=GetClientFromRequest(aRequest); + aClient.FID:=GetNextCounter; + FClients.Add(aClient); + SendClientObjectResponse(aClient,aResponse); + FLastClient:=aClient; + DoEvent(@DoOnClientAdded); +end; + +function TIDEServer.Do404(is404: boolean; aResponse: TResponse): Boolean; + +begin + Result:=is404; + if Result then + begin + aResponse.Code:=404; + aResponse.Codetext:='Not found'; + aResponse.SendResponse; + end; +end; + +procedure TIDEServer.DoDeleteClient(ARequest: TRequest; AResponse: TResponse); + +Var + aClientID : Int64; + aClient : TIDEClient; + +begin + aClientID:=CheckClient(aRequest); + aClient:=TIDEClient(FClients.FindID(aClientID)); + if Do404(not Assigned(aClient),aResponse) then + exit; + FLastClient:=aClient; + DoEvent(@DoOnClientRemoved); + FClients.Remove(aClient); +end; + + +procedure TIDEServer.RegisterRoutes; + +begin + // get command + HTTPRouter.RegisterRoute(SIDEURL+'Quit',rmGet,@DoQuit); + HTTPRouter.RegisterRoute(SIDEURL+'Client/',rmPost,@DoPostClient); + HTTPRouter.RegisterRoute(SIDEURL+'Client/:Client',rmDelete,@DoDeleteClient); + HTTPRouter.RegisterRoute(SIDEURL+'Command/:Client/',rmGet,@DoGetCommand); + // PUT command for confirm. + HTTPRouter.RegisterRoute(SIDEURL+'Command/:Client/:ID',rmPut,@DoPutCommand); + // POST action + HTTPRouter.RegisterRoute(SIDEURL+'Action/:Client/:ID',rmPost,@DoPostAction); + HTTPRouter.RegisterRoute(SIDEURL+'Action/:Client/:ID',rmDelete,@DoDeleteAction); + // GET file + HTTPRouter.RegisterRoute(SFilesURL+'*',rmGet,@DoGetFile,true); + HTTPRouter.BeforeRequest:=@DoRouteRequest; +end; + +destructor TIDEServer.Destroy; +begin + Active:=False; + While Active do + Sleep(20); + FreeAndNil(FActions); + FreeAndNil(FCommands); + FreeAndNil(FClients); + inherited Destroy; +end; + +function TIDEServer.GetNextCounter: Int64; +begin + Inc(FIDCounter); + Result:=FIDCounter; +end; + +function TIDEServer.SendCommand(aCommand: TIDECommand): Int64; +begin + Result:=GetNextCounter; + aCommand.ID:=Result; + FCommands.Add(aCommand); +end; + +function TIDEServer.DeleteAction(aID: Int64; const aClientID: Int64): Boolean; + +Var + P : TIDEAction; + L : TList; + I : Integer; + +begin + P:=nil; + L:=FActions.LockList; + try + I:=L.Count-1; + While (I>=0) and (P=Nil) do + begin + P:=TIDEAction(L[i]); + if P.ID<>AID then P:=Nil; + Dec(i) + end; + finally + L.Free; + end; + Result:=(P<>Nil) and ((aClientID=-1) or (P.ClientID=aClientID)); + if Result then + FActions.Remove(P); +end; + +procedure TIDEServer.GetClientActions(aClientID: Int64; aList: TFPList); + +Var + P : TIDEAction; + L : TList; + I : Integer; +begin + P:=nil; + L:=FActions.LockList; + try + I:=L.Count-1; + While (I>=0) and (P=Nil) do + begin + P:=TIDEAction(L[i]); + if P.ClientID=aClientID then + begin + aList.Add(P); + L.Delete(I); + end; + Dec(i); + end; + finally + L.Free; + end; +end; + +end. + diff --git a/packages/webwidget/htmlwidgets.pp b/packages/webwidget/htmlwidgets.pp new file mode 100644 index 0000000..7d18a41 --- /dev/null +++ b/packages/webwidget/htmlwidgets.pp @@ -0,0 +1,1587 @@ +unit htmlwidgets; + +{$mode objfpc} + +interface + +uses + Classes, SysUtils, webwidget, js, web; + +Type + + { TButtonWidget } + + TButtonWidget = Class(TWebWidget) + private + FText: String; + procedure SetText(AValue: String); + Protected + Procedure SetName(const NewName: TComponentName); override; + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Public + Procedure Click; + Function HTMLTag : String; override; + Property Text : String Read FText Write SetText; + end; + + { TViewPort } + + TViewPort = Class(TCustomWebWidget) + Private + Class var FInstance : TViewPort; + Protected + Class Function FixedParent : TJSHTMLElement; override; + Class Function FixedElement : TJSHTMLElement; override; + Function DoRenderHTML(aParent,aElement : TJSHTMLElement) :TJSHTMLElement; override; + Public + Constructor Create (aOwner: TComponent); override; + Function HTMLTag : String; override; + Class Function Instance : TViewPort; + Property Element; + end; + + { TWebPage } + + TWebPage = Class(TCustomWebWidget) + private + Protected + Class Function DefaultParentElement: TJSHTMLElement; override; + Class Function DefaultParent : TCustomWebWidget; override; + Procedure DoUnRender(aParent : TJSHTMLElement) ; override; + Public + Constructor Create(AOwner : TComponent); override; + Function HTMLTag : String; override; + // Later on, allow IFrame; + Published + Property ParentID; + Property ElementID; + Property Classes; + Property Styles; + Property StyleRefresh; + Property Visible; + // Events + Property BeforeRenderHTML; + Property AfterRenderHTML; + Property OnAbort; + Property OnAnimationCancel; + Property OnAnimationEnd; + Property OnAnimationIteration; + Property OnAnimationStart; + Property OnAuxClick; + Property OnBlur; + Property OnCancel; + Property OnCanPlay; + Property OnCanPlayThrough; + Property OnChange; + Property OnClick; + Property OnCompositionEnd; + Property OnCompositionStart; + Property OnCompositionUpdate; + Property OnContextMenu; + Property OnCopy; + Property OnCut; + Property OnCueChange; + Property OnDblClick; + Property OnDurationChange; + Property OnEnded ; + Property OnError ; + Property OnFocus; + Property OnFocusIn ; + Property OnFocusOut ; + Property OnGotPointerCapture; + Property OnInput; + Property OnInvalid; + Property OnKeyDown; + Property OnKeyPress; + Property OnKeyUp; + Property OnLoad; + Property OnLoadedData; + Property OnLoadedMetaData; + Property OnLoadend; + Property OnLoadStart; + Property OnLostPointerCapture; + Property OnMouseDown; + Property OnMouseEnter; + Property OnMouseLeave; + Property OnMouseMove; + Property OnMouseOut; + Property OnMouseUp; + Property OnOverFlow; + Property OnPaste; + Property OnPause; + Property OnPlay; + Property OnPointerCancel; + Property OnPointerDown; + Property OnPointerEnter; + Property OnPointerLeave; + Property OnPointerMove; + Property OnPointerOut; + Property OnPointerOver; + Property OnPointerUp; + Property OnReset; + Property OnResize; + Property OnScroll; + Property OnSelect; + Property OnSubmit; + Property OnTouchStart; + Property OnTransitionCancel; + Property OnTransitionEnd; + Property OnTransitionRun; + Property OnTransitionStart; + Property OnWheel; + end; + + { TCustomInputWidget } + + TCustomInputWidget = Class(TWebWidget) + private + FValue : String; + FValueName : String; + FText : String; + FReadOnly : Boolean; + FRequired : Boolean; + function GetReadOnly: Boolean; + function GetRequired: Boolean; + function GetText: String; + function GetValue: String; + function GetValueName: String; + procedure SetReadonly(AValue: Boolean); + procedure SetRequired(AValue: Boolean); + procedure SetText(AValue: String); + procedure SetValue(AValue: String); + function GetInputElement: TJSHTMLInputElement; + procedure SetValueName(AValue: String); + Protected + Procedure SetName(const NewName: TComponentName); override; + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Property InputElement : TJSHTMLInputElement Read GetInputElement; + // Text to show (checkbox etc). Enable in descendents as needed + Property Text : String Read GetText Write SetText; + Public + function InputType : String; virtual; abstract; + Function HTMLTag : String; override; + // Value as string + Property Value : String Read GetValue Write SetValue; + // Value Name to use when submitting using form. + Property ValueName : String Read GetValueName Write SetValueName; + Property ReadOnly : Boolean Read GetReadOnly Write SetReadonly; + Property Required : Boolean Read GetRequired Write SetRequired; + end; + + { TTextInputWidget } + + TInputTextType = (ittText,ittPassword,ittNumber,ittEmail,ittSearch,ittTelephone,ittURL,ittColor); + TTextInputWidget = class(TCustomInputWidget) + private + FMaxLength : Integer; + FMinLength : Integer; + FTextType : TInputTextType; + function GetAsNumber: NativeInt; + function GetMaxLength: NativeInt; + function GetMinLength: NativeInt; + function GetTextType: TInputTextType; + procedure SetAsNumber(AValue: NativeInt); + procedure SetMaxLength(AValue: NativeInt); + procedure SetMinLength(AValue: NativeInt); + procedure SetTextType(AValue: TInputTextType); + Protected + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Public + Class Function AllowChildren : Boolean; override; + function InputType : String; override; + Published + Property Value; + Property ValueName; + Property TextType : TInputTextType Read GetTextType Write SetTextType; + property AsNumber : NativeInt Read GetAsNumber Write SetAsNumber; + Property MaxLength : NativeInt Read GetMaxLength Write SetMaxLength; + Property MinLength : NativeInt Read GetMinLength Write SetMinLength; + // Todo: List support + end; + + + { TButtonInputWidget } + TInputButtonType = (ibtSubmit,ibtReset,ibtImage); + TInputButtonTypes = set of TInputButtonType; + + TButtonInputWidget = class(TCustomInputWidget) + private + FButtonType: TInputButtonType; + FSrc: String; + procedure SetButtonType(AValue: TInputButtonType); + procedure SetSrc(AValue: String); + Public + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + function InputType : String; override; + Class Function AllowChildren : Boolean; override; + Published + Property ButtonType : TInputButtonType Read FButtonType Write SetButtonType; + Property Value; + Property ValueName; + Property Src : String Read FSrc Write SetSrc; + end; + + { TCheckableInputWidget } + + TCheckableInputWidget = class(TCustomInputWidget) + private + FChecked: Boolean; + function GetChecked: Boolean; + procedure SetChecked(AValue: Boolean); + Protected + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Public + Property Value; + Property ValueName; + Property Checked : Boolean Read GetChecked Write SetChecked; + Property Text; + end; + + { TRadioInputWidget } + + TRadioInputWidget = class(TCheckableInputWidget) + private + Public + function InputType : String; override; + Published + Property Value; + Property ValueName; + Property Checked; + Property Text; + end; + + { TCheckboxInputWidget } + + TCheckboxInputWidget = class(TCheckableInputWidget) + private + Public + function InputType : String; override; + Published + Property Value; + Property ValueName; + Property Checked; + Property Text; + end; + + + { TDateInputWidget } + + TDateInputWidget = class(TCustomInputWidget) + private + FDate: TDateTime; + function GetDate: TDateTime; + procedure SetDate(AValue: TDateTime); + Public + function InputType : String; override; + Class Function AllowChildren : Boolean; override; + Published + Property ValueName; + Property Date : TDateTime Read GetDate Write SetDate; + end; + + { TFileInputWidget } + TFileInfo = record + Name : String; + TimeStamp : TDateTime; + FileType : String; + Size : NativeInt; + end; + + TFileInputWidget = class(TCustomInputWidget) + private + FMultiple: Boolean; + function GetFileCount: Integer; + function GetFileDate(aIndex : Integer): TDateTime; + function GetFileInfo(aIndex : Integer): TFileInfo; + function GetFileName(aIndex : Integer): String; + function GetFileSize(aIndex : Integer): NativeInt; + function GetFileType(aIndex : Integer): String; + function GetMultiple: Boolean; + procedure SetMultiple(AValue: Boolean); + Protected + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Public + Class Function AllowChildren : Boolean; override; + function InputType : String; override; + Property FileCount : Integer read GetFileCount; + Property Files[aIndex : Integer] : String Read GetFileName; + Property FileSizes[aIndex : Integer] : NativeInt Read GetFileSize; + Property FileTypes[aIndex : Integer] : String Read GetFileType; + Property FileDates[aIndex : Integer] : TDateTime Read GetFileDate; + Property FileInfos[aIndex : Integer] : TFileInfo Read GetFileInfo; + Published + Property ValueName; + Property Multiple : Boolean Read GetMultiple Write SetMultiple; + end; + + { THiddenInputWidget } + + THiddenInputWidget = class(TCustomInputWidget) + Public + Class Function AllowChildren : Boolean; override; + function InputType : String; override; + Published + Property ValueName; + Property Value; + end; + + { TTextAreaWidget } + + TTextAreaWrap = (tawSoft,tawHard,tawOff); + TTextAreaWidget = Class(TWebWidget) + private + FLines: TStrings; + FIgnoreChanges : Boolean; + FMaxLength: Cardinal; + FValueName : String; + FRows, + FColumns : Cardinal; + FWrap: TTextAreaWrap; + FRequired, + FReadOnly : Boolean; + procedure ApplyWrap(aElement: TJSHTMLTextAreaElement); + procedure DoLineChanges(Sender: TObject); + function GetColumns: Cardinal; + function GetLines: TStrings; + function GetReadOnly: Boolean; + function GetRequired: Boolean; + function GetRows: Cardinal; + function GetText: String; + function GetValueName: string; + procedure SetColumns(AValue: Cardinal); + procedure SetLines(AValue: TStrings); + procedure SetMaxLength(AValue: Cardinal); + procedure SetReadonly(AValue: Boolean); + procedure SetRequired(AValue: Boolean); + procedure SetRows(AValue: Cardinal); + procedure SetText(AValue: String); + procedure SetValueName(AValue: string); + Function GetTextArea : TJSHTMLTextAreaElement; + procedure SetWrap(AValue: TTextAreaWrap); + Protected + procedure ApplyLines(aElement: TJSHTMLTextAreaElement); + procedure LinesFromHTML(aHTML : String); + Procedure SetName(const NewName: TComponentName); override; + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Property TextArea :TJSHTMLTextAreaElement Read GetTextArea; + Public + Constructor Create(aOwner : TComponent); override; + Destructor Destroy; override; + Class Function AllowChildren : Boolean; override; + Function HTMLTag : String; override; + Property InnerHTML : String Read GetText Write SetText; + Published + Property ValueName : string Read GetValueName Write SetValueName; + Property Rows : Cardinal Read GetRows Write SetRows; + Property Columns : Cardinal Read GetColumns Write SetColumns; + Property Lines : TStrings Read GetLines Write SetLines; + Property MaxLength : Cardinal Read FMaxLength Write SetMaxLength; + Property Wrap : TTextAreaWrap Read FWrap Write SetWrap; + Property ReadOnly : Boolean Read GetReadOnly Write SetReadonly; + Property Required : Boolean Read GetRequired Write SetRequired; + end; + + { TImageWidget } + + TImageWidget = class(TWebWidget) + private + FHeight: Integer; + FWidth: Integer; + FSrc : String; + function GetHeight: Integer; + function GetImg: TJSHTMLImageElement; + function GetSrc: String; + function GetWidth: Integer; + procedure SetHeight(AValue: Integer); + procedure SetSrc(AValue: String); + procedure SetWidth(AValue: Integer); + Protected + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Property ImgElement : TJSHTMLImageElement Read GetImg; + Public + Function HTMLTag : String; override; + Published + Property Src : String Read GetSrc Write SetSrc; + Property Width : Integer Read GetWidth Write SetWidth; + Property Height : Integer Read GetHeight Write SetHeight; + end; + + { TSelectWidget } + TJSHTMLOptionElementArray = Array of TJSHTMLOptionElement; + TSelectWidget = class(TWebWidget) + private + FItems : TStrings; + FValues : TStrings; + FOptions : TJSHTMLOptionElementArray; + FSelectedIndex : Integer; + FMultiple : Boolean; + function GetMultiple: Boolean; + function GetSelected(Index : Integer): Boolean; + function GetSelectedIndex: Integer; + function GetItems: TStrings; + function GetSelect: TJSHTMLSelectElement; + function GetSelectionCount: Integer; + function GetSelectionItem(aIndex : Integer): String; + function GetSelectionValue(aIndex : Integer): String; + function GetValues: TStrings; + procedure OptionsChanged(Sender: TObject); + procedure SetMultiple(AValue: Boolean); + procedure SetSelected(Index : Integer; AValue: Boolean); + procedure SetSelectedIndex(AValue: Integer); + procedure setItems(AValue: TStrings); + procedure setValues(AValue: TStrings); + Protected + Procedure BuildOptions(aSelect : TJSHTMLSelectElement); virtual; + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Property SelectElement : TJSHTMLSelectElement Read GetSelect; + Property Options : TJSHTMLOptionElementArray Read Foptions; + Public + Constructor Create(aOWner : TComponent); override; + Destructor Destroy; override; + Function HTMLTag : String; override; + // Items that are selected + Property Selected[Index : Integer] : Boolean Read GetSelected Write SetSelected; + Property SelectionCount : Integer Read GetSelectionCount; + Property SelectionValue[aIndex : Integer] : String Read GetSelectionValue; + Property SelectionItem[aIndex : Integer] : String Read GetSelectionItem; + Published + Property Items : TStrings Read GetItems Write setItems; + Property Values : TStrings Read GetValues Write setValues; + property SelectedIndex : Integer Read GetSelectedIndex Write SetSelectedindex; + Property Multiple : Boolean Read GetMultiple Write SetMultiple; + end; + + { TLabelWidget } + + TLabelWidget = Class(TWebWidget) + private + FLabelFor: TWebWidget; + FText: String; + function GetLabelEl: TJSHTMLLabelElement; + function GetText: String; + procedure SetLabelFor(AValue: TWebWidget); + procedure SetText(AValue: String); + Protected + procedure ApplyLabelFor(aLabelElement: TJSHTMLLabelElement); + Procedure Notification(AComponent: TComponent; Operation: TOperation); override; + Procedure SetName(const NewName: TComponentName); override; + Procedure ApplyWidgetSettings(aElement: TJSHTMLElement); override; + Property LabelElement : TJSHTMLLabelElement Read GetLabelEl; + Public + Function HTMLTag : String; override; + Property Text : String Read GetText Write SetText; + Property LabelFor : TWebWidget Read FLabelFor Write SetLabelFor; + end; + + +Function ViewPort : TViewPort; + +implementation + +uses DateUtils; + +resourcestring + SErrInvalidIndex = 'Index %s not in valid range of [0..%d]'; + +Function ViewPort : TViewPort; + +begin + Result:=TViewPort.Instance; +end; + +{ TLabelWidget } + +procedure TLabelWidget.ApplyLabelFor(aLabelElement : TJSHTMLLabelElement); + +begin + if Assigned(FlabelFor) then + begin + FlabelFor.EnsureElement; + aLabelElement.for_:=FlabelFor.ElementID; + end + else + aLabelElement.for_:=''; +end; + +procedure TLabelWidget.SetLabelFor(AValue: TWebWidget); +begin + if (FLabelFor=AValue) then Exit; + if Assigned(FLabelFor) then + FLabelFor.RemoveFreeNotification(Self); + FLabelFor:=AValue; + if Assigned(FLabelFor) then + FLabelFor.FreeNotification(Self); + If IsRendered then + ApplyLabelFor(LabelElement); +end; + +function TLabelWidget.GetText: String; +begin + if IsElementDirty then + FText:=Element.InnerText; + Result:=FText; +end; + +function TLabelWidget.GetLabelEl: TJSHTMLLabelElement; +begin + Result:=TJSHTMLLabelElement(Element); +end; + +procedure TLabelWidget.SetText(AValue: String); +begin + If Text=aValue then exit; + Ftext:=aValue; + If IsRendered then + Element.innerText:=aValue; +end; + +procedure TLabelWidget.Notification(AComponent: TComponent; Operation: TOperation); +begin + inherited Notification(AComponent, Operation); + if (Operation=opRemove) and (aComponent=FLabelFor) then + FLabelFor:=Nil; +end; + +procedure TLabelWidget.SetName(const NewName: TComponentName); + +Var + Old : String; + +begin + Old:=Name; + inherited SetName(NewName); + if (csDesigning in ComponentState) then + if Old=Text then + Text:=Old; +end; + +procedure TLabelWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); + +var + lbl : TJSHTMLLabelElement absolute aElement; + +begin + inherited ApplyWidgetSettings(aElement); + lbl.InnerText:=Text; + ApplyLabelFor(Lbl); +end; + + +function TLabelWidget.HTMLTag: String; +begin + Result:='label'; +end; + +{ TSelectWidget } + +function TSelectWidget.GetSelectedIndex: Integer; +begin + if IsRendered then + FSelectedIndex:=SelectElement.selectedIndex; + Result:=FSelectedIndex +end; + +function TSelectWidget.GetMultiple: Boolean; + +begin + if IsElementDirty then + FMultiple:=SelectElement.multiple; + Result:=FMultiple; +end; + +function TSelectWidget.GetSelected(Index : Integer): Boolean; +begin + if (Index<0) or (Index>=Length(Foptions)) then + Raise EWidgets.CreateFmt(SErrInvalidIndex,[Index,Length(Foptions)-1]); + Result:=FOptions[Index].Selected +end; + +function TSelectWidget.GetItems: TStrings; +begin + Result:=FItems; +end; + +function TSelectWidget.GetSelect: TJSHTMLSelectElement; +begin + Result:=TJSHTMLSelectElement(Element); +end; + +function TSelectWidget.GetSelectionCount: Integer; +begin + Result:=SelectElement.selectedOptions.length; +end; + +function TSelectWidget.GetSelectionItem(aIndex : Integer): String; +begin + if (aIndex<0) or (aindex>=GetSelectionCount) then + Raise EWidgets.CreateFmt(SErrInvalidIndex,[aIndex,GetSelectionCount-1]); + Result:=TJSHTMLOptionElement(SelectElement.selectedOptions.item(aIndex)).innerText; +end; + +function TSelectWidget.GetSelectionValue(aIndex : Integer): String; +begin + if (aIndex<0) or (aindex>=GetSelectionCount) then + Raise EWidgets.CreateFmt(SErrInvalidIndex,[aIndex,GetSelectionCount-1]); + Result:=TJSHTMLOptionElement(SelectElement.selectedOptions.item(aIndex)).value; +end; + +function TSelectWidget.GetValues: TStrings; +begin + Result:=FValues; +end; + +procedure TSelectWidget.OptionsChanged(Sender: TObject); +begin + if IsRendered then + BuildOptions(SelectElement); +end; + +procedure TSelectWidget.SetMultiple(AValue: Boolean); +begin + If (AValue=Multiple) then exit; + FMultiple:=aValue; + If IsRendered then + SelectElement.multiple:=FMultiple; +end; + +procedure TSelectWidget.SetSelected(Index : Integer; AValue: Boolean); +begin + if (Index<0) or (Index>=Length(Foptions)) then + Raise EWidgets.CreateFmt(SErrInvalidIndex,[Index,Length(Foptions)-1]); + FOptions[Index].Selected:=True; +end; + +procedure TSelectWidget.SetSelectedIndex(AValue: Integer); + +begin + if (SelectedIndex=aValue) then + Exit; + FSelectedIndex:=aValue; + if IsRendered then + SelectElement.SelectedIndex:=FSelectedIndex; +end; + +procedure TSelectWidget.setItems(AValue: TStrings); +begin + If (AValue=FItems) then exit; + FItems.Assign(aValue); +end; + + +procedure TSelectWidget.setValues(AValue: TStrings); +begin + If (AValue=FValues) then exit; + FValues.Assign(aValue); +end; + +procedure TSelectWidget.BuildOptions(aSelect: TJSHTMLSelectElement); + +Var + O : TJSHTMLOptionElement; + Itms,Vals : TStrings; + I,Idx : Integer; + +begin + // Clear + SetLength(FOptions,0); + aSelect.InnerHTML:=''; + // Rebuild + Itms:=Fitems; + Vals:=FValues; + Idx:=FSelectedIndex; + SetLength(Foptions,Itms.Count); + For I:=0 to Itms.Count-1 do + begin + O:=TJSHTMLOptionElement(CreateElement('option','')); + FOptions[I]:=O; + O.innerText:=Itms[I]; + if I<Vals.Count then + O.value:=Vals[i]; + if I=Idx then + O.selected:=True; + aSelect.AppendChild(O); + end; +end; + +procedure TSelectWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); + +Var + el : TJSHTmlSelectElement absolute aElement; + +begin + inherited ApplyWidgetSettings(aElement); + BuildOptions(el); +end; + +constructor TSelectWidget.Create(aOWner: TComponent); +begin + inherited Create(aOWner); + FItems:=TStringList.Create; + TStringList(FItems).OnChange:=@OptionsChanged; + FValues:=TStringList.Create; + TStringList(FValues).OnChange:=@OptionsChanged; +end; + +destructor TSelectWidget.Destroy; +begin + inherited Destroy; +end; + +function TSelectWidget.HTMLTag: String; +begin + Result:='select'; +end; + +{ TImageWidget } + +function TImageWidget.GetHeight: Integer; +begin + if IsElementDirty then + FHeight:=ImgElement.Height; + Result:=Fheight; +end; + +function TImageWidget.GetImg: TJSHTMLImageElement; +begin + Result:=TJSHTMLImageElement(Element); +end; + +function TImageWidget.GetSrc: String; +begin + if IsElementDirty then + FSrc:=ImgElement.Src; + Result:=FSrc; +end; + +function TImageWidget.GetWidth: Integer; +begin + if IsElementDirty then + FWidth:=ImgElement.Width; + Result:=FWidth; +end; + +procedure TImageWidget.SetHeight(AValue: Integer); +begin + if AValue=Height then exit; + FHeight:=AValue; + If isrendered then + ImgElement.Height:=aValue; +end; + +procedure TImageWidget.SetSrc(AValue: String); +begin + if AValue=Src then exit; + FSrc:=AValue; + If isrendered then + ImgElement.Src:=FSrc; +end; + +procedure TImageWidget.SetWidth(AValue: Integer); +begin + if AValue=Width then exit; + FWidth:=AValue; + If isrendered then + ImgElement.Width:=aValue; +end; + +procedure TImageWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); + +var + img : TJSHTMLImageElement absolute aElement; + +begin + inherited ApplyWidgetSettings(aElement); + Img.Src:=FSrc; + Img.Height:=FHeight; + Img.Width:=FWidth; +end; + +function TImageWidget.HTMLTag: String; +begin + Result:='img'; +end; + +{ TTextAreaWidget } + +procedure TTextAreaWidget.SetLines(AValue: TStrings); +begin + if FLines=AValue then Exit; + FLines.Assign(AValue); +end; + +procedure TTextAreaWidget.SetMaxLength(AValue: Cardinal); +begin + if FMaxLength=AValue then Exit; + FMaxLength:=AValue; + if IsRendered then + TextArea.maxLength:=aValue; +end; + +procedure TTextAreaWidget.SetReadonly(AValue: Boolean); +begin + If aValue=ReadOnly then exit; + FReadOnly:=aValue; + if IsRendered then + TextArea.Readonly:=FReadOnly; +end; + +procedure TTextAreaWidget.SetRequired(AValue: Boolean); +begin + If aValue=Required then exit; + FRequired:=aValue; + if IsRendered then + TextArea.Required:=FRequired; +end; + +function TTextAreaWidget.GetColumns: Cardinal; +begin + if IsElementDirty then + FColumns:=TextArea.Cols; + Result:=FColumns; +end; + +procedure TTextAreaWidget.DoLineChanges(Sender: TObject); +begin + if isRendered and not FIgnoreChanges then + ApplyLines(TextArea); +end; + + +function TTextAreaWidget.GetLines: TStrings; +begin + // We may want to change this to something more efficient. Maybe handle onchange + // Note that if yo + if IsElementDirty then + begin + FIgnoreChanges:=True; + try + LinesFromHTML(Element.InnerHTml); + finally + FIgnoreChanges:=False; + end; + end; + Result:=FLines; +end; + +function TTextAreaWidget.GetReadOnly: Boolean; +begin + if IsElementDirty then + FReadonly:=TextArea.readOnly; + Result:=FReadonly; +end; + +function TTextAreaWidget.GetRequired: Boolean; +begin + if IsElementDirty then + FRequired:=TextArea.Required; + Result:=FRequired; +end; + +function TTextAreaWidget.GetRows: Cardinal; +begin + if IsElementDirty then + FRows:=TextArea.Rows; + Result:=FRows; +end; + +function TTextAreaWidget.GetText: String; +begin + if IsElementDirty then + Result:=Element.InnerHTML + else + Result:=FLines.Text; +end; + +function TTextAreaWidget.GetValueName: string; +begin + if IsElementDirty then + FValueName:=Element.Name; + Result:=FValueName; +end; + +procedure TTextAreaWidget.SetColumns(AValue: Cardinal); +begin + if AValue=FColumns then exit; + FColumns:=aValue; + if isRendered then + TextArea.cols:=aValue; +end; + +procedure TTextAreaWidget.SetRows(AValue: Cardinal); +begin + if AValue=FRows then exit; + FRows:=aValue; + if isRendered then + TextArea.Rows:=aValue; +end; + +procedure TTextAreaWidget.SetText(AValue: String); +begin + if isRendered then + element.InnerText:=aValue + else + LinesFromHTML(aValue); +end; + +procedure TTextAreaWidget.SetValueName(AValue: string); +begin + if aValue=FValueName then exit; + FValueName:=aValue; + if IsRendered then + TextArea.Name:=aValue; +end; + +procedure TTextAreaWidget.SetName(const NewName: TComponentName); + +var + Old : String; +begin + Old:=Name; + inherited SetName(NewName); + if csDesigning in ComponentState then + begin + if (FLines.Count=0) then + FLines.Add(Name) + else if (FLines.Count=1) and (FLines[0]=Old) then + FLines[0]:=Name; + end; +end; + +procedure TTextAreaWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); + +var + area : TJSHTMLTextAreaElement absolute aElement; + +begin + inherited ApplyWidgetSettings(aElement); + if FMaxLength>0 then + area.maxlength:=FMaxLength; + if FColumns>0 then + area.cols:=FColumns; + if FRows>0 then + area.Rows:=FRows; + if FLines.Count>0 then + ApplyLines(area); + if FValueName<>'' then + area.Name:=FValueName; + area.Readonly:=FReadOnly; + area.Required:=FRequired; + ApplyWrap(area); +end; + + +constructor TTextAreaWidget.Create(aOwner: TComponent); +begin + inherited Create(aOwner); + FLines:=TStringList.Create; + TStringList(FLines).OnChange:=@DoLineChanges; + FColumns:=50; + FRows:=10; +end; + +destructor TTextAreaWidget.Destroy; +begin + FreeAndNil(Flines); + inherited; +end; + +class function TTextAreaWidget.AllowChildren: Boolean; +begin + Result:=False; +end; + +function TTextAreaWidget.GetTextArea: TJSHTMLTextAreaElement; +begin + Result:=TJSHTMLTextAreaElement(Element); +end; + +procedure TTextAreaWidget.ApplyWrap(aElement :TJSHTMLTextAreaElement); + +Const + Wraps : Array[TTextAreaWrap] of string = ('soft','hard','off'); + +begin + aElement.wrap:=Wraps[FWrap]; +end; + +procedure TTextAreaWidget.ApplyLines(aElement: TJSHTMLTextAreaElement); +begin + aElement.innerHTML:=FLines.Text; +end; + +procedure TTextAreaWidget.LinesFromHTML(aHTML: String); +begin + FLines.Text:= StringReplace(aHTML,'<br>',sLineBreak,[rfIgnoreCase,rfReplaceAll]); +end; + + + +procedure TTextAreaWidget.SetWrap(AValue: TTextAreaWrap); + +begin + if FWrap=AValue then Exit; + FWrap:=AValue; + if IsRendered then + ApplyWrap(TextArea) +end; + +function TTextAreaWidget.HTMLTag: String; +begin + result:='textarea'; +end; + +{ TCheckboxInputWidget } + +function TCheckboxInputWidget.InputType: String; +begin + Result:='checkbox'; +end; + +{ TRadioInputWidget } + +function TRadioInputWidget.InputType: String; +begin + Result:='radio'; +end; + +{ THiddenInputWidget } + +class function THiddenInputWidget.AllowChildren: Boolean; +begin + Result:=False; +end; + +function THiddenInputWidget.InputType: String; +begin + Result:='hidden'; +end; + +{ TFileInputWidget } + +procedure TFileInputWidget.SetMultiple(AValue: Boolean); +begin + if FMultiple=AValue then Exit; + FMultiple:=AValue; + if Isrendered then + InputElement.multiple:=FMultiple; +end; + +function TFileInputWidget.GetMultiple: Boolean; +begin + if IsElementDirty then + FMultiple:=InputElement.multiple; + Result:=FMultiple; +end; + + +function TFileInputWidget.GetFileName(aIndex : Integer): String; +begin + Result:=InputElement.files.Files[aIndex].name; +end; + +function TFileInputWidget.GetFileSize(aIndex : Integer): NativeInt; +begin + Result:=InputElement.files.Files[aIndex].Size; +end; + +function TFileInputWidget.GetFileType(aIndex : Integer): String; +begin + Result:=InputElement.files.Files[aIndex]._Type; +end; + +function TFileInputWidget.GetFileCount: Integer; +begin + Result:=InputElement.files.Length; +end; + +function TFileInputWidget.GetFileDate(aIndex : Integer): TDateTime; +begin + Result:=JSDateToDateTime(InputElement.files.Files[aIndex].lastModifiedDate); +end; + +function TFileInputWidget.GetFileInfo(aIndex : Integer): TFileInfo; + +Var + f : TJSHTMLFile; + +begin + F:=InputElement.files.Files[aIndex]; + Result.Name:=F.name; + Result.Size:=F.size; + Result.FileType:=F._type; + Result.TimeStamp:= JSDateToDateTime(F.lastModifiedDate); +end; + + +procedure TFileInputWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); + +Var + Old : String; + +begin + Old:=FValue; + FValue:=''; + try + inherited ApplyWidgetSettings(aElement); + TJSHTMLInputElement(aElement).multiple:=FMultiple; + finally + FValue:=Old; + end; +end; + +class function TFileInputWidget.AllowChildren: Boolean; +begin + Result:=False; +end; + +function TFileInputWidget.InputType: String; +begin + Result:='file'; +end; + + +{ TDateInputWidget } + +function TDateInputWidget.GetDate: TDateTime; + +var + aDate : TDateTime; + +begin + if IsElementDirty then + begin + aDate:=ScanDateTime('yyyy-mm-dd',Value); + if aDate<>0 then + FDate:=aDate; + end; + Result:=FDate; +end; + + +procedure TDateInputWidget.SetDate(AValue: TDateTime); +begin + FDate:=aValue; + Value:=FormatDateTime('yyyy-mm-dd',FDate); +end; + +function TDateInputWidget.InputType: String; +begin + Result:='date'; +end; + +class function TDateInputWidget.AllowChildren: Boolean; +begin + Result:=False; +end; + +{ TCheckableInputWidget } + + +procedure TCheckableInputWidget.SetChecked(AValue: Boolean); +begin + // Get actual value + if Checked=AValue then Exit; + if isRendered then + InputElement.checked:=aValue; + FChecked:=AValue; +end; + +procedure TCheckableInputWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); +begin + inherited ApplyWidgetSettings(aElement); + TJSHTMLInputElement(aElement).Checked:=FChecked; +end; + + +function TCheckableInputWidget.GetChecked: Boolean; +begin + if IsElementDirty then + FChecked:=InputElement.Checked; + Result:=FChecked; +end; + + +{ TButtonInputWidget } + +procedure TButtonInputWidget.SetButtonType(AValue: TInputButtonType); +begin + if FButtonType=AValue then Exit; + FButtonType:=AValue; + if IsRendered then + Refresh; +end; + +procedure TButtonInputWidget.SetSrc(AValue: String); +begin + if FSrc=AValue then Exit; + FSrc:=AValue; + if IsRendered and (ButtonType=ibtImage) then + Element.setAttribute('src',FSrc); +end; + +procedure TButtonInputWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); +begin + inherited ApplyWidgetSettings(aElement); + if ButtonType=ibtImage then + aElement.setAttribute('src',FSrc); +end; + +function TButtonInputWidget.InputType: String; + +Const + Types : Array[TInputButtonType] of string = ('submit','reset','image'); + +begin + Result:=Types[FButtonType] +end; + +class function TButtonInputWidget.AllowChildren: Boolean; +begin + Result:=False; +end; + +{ TTextInputWidget } + +function TTextInputWidget.GetAsNumber: NativeInt; +begin + Result:=StrToIntDef(Value,0); +end; + +function TTextInputWidget.GetMaxLength: NativeInt; +begin + if IsElementDirty then + FMaxLength:=InputElement.maxLength; + Result:=FMaxLength; +end; + +function TTextInputWidget.GetMinLength: NativeInt; +begin + if IsElementDirty then + FMinLength:=InputElement.minLength; + Result:=FMinLength; +end; + +function TTextInputWidget.GetTextType: TInputTextType; +begin + Result:=FTextType; +end; + +procedure TTextInputWidget.SetAsNumber(AValue: NativeInt); +begin + Value:=IntToStr(aValue); +end; + + +procedure TTextInputWidget.SetMaxLength(AValue: NativeInt); +begin + if (aValue=FMaxLength) then exit; + FMaxLength:=aValue; + if IsRendered then + InputElement.maxLength:=FMaxLength; +end; + +procedure TTextInputWidget.SetMinLength(AValue: NativeInt); +begin + if (aValue=FMinLength) then exit; + FMinLength:=aValue; + if IsRendered then + InputElement.minLength:=FMinLength; +end; + +procedure TTextInputWidget.SetTextType(AValue: TInputTextType); +begin + if aValue=FTextType then exit; + FTextType:=aValue; + if IsRendered then + Refresh; +end; + +procedure TTextInputWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); + +var + inp : TJSHTMLInputElement absolute aElement; + +begin + inherited ApplyWidgetSettings(aElement); + if FMaxLength<>0 then + inp.maxLength:=FMaxLength; + if FMinLength<>0 then + inp.minLength:=FMinLength; +end; + +class function TTextInputWidget.AllowChildren: Boolean; +begin + Result:=False; +end; + +function TTextInputWidget.InputType: String; + +Const + Types : Array[TInputTextType] of string = + ('text','password','number','email','search','tel','url','color'); + +begin + Result:=Types[FTextType]; +end; + +{ TWebPage } + +constructor TWebPage.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + Classes:='WebPage'; +end; + + +class function TWebPage.DefaultParentElement: TJSHTMLElement; +begin + Result:=TViewport.Instance.Element; +end; + +class function TWebPage.DefaultParent: TCustomWebWidget; +begin + Result:=TViewport.Instance; +end; + +procedure TWebPage.DoUnRender(aParent: TJSHTMLElement); +begin + inherited DoUnRender(aParent); +end; + +function TWebPage.HTMLTag: String; +begin + Result:='div'; +end; + + +{ TViewPort } + +function TViewPort.HTMLTag: String; +begin + Result:='body'; +end; + +class function TViewPort.FixedParent: TJSHTMLElement; +begin + Result:=TJSHTMLElement(Document.documentElement); +end; + +class function TViewPort.FixedElement: TJSHTMLElement; +begin + Result:=TJSHTMLElement(Document.Body); +end; + +function TViewPort.DoRenderHTML(aParent, aElement: TJSHTMLElement): TJSHTMLElement; +begin + Result:=FixedElement; +end; + +constructor TViewPort.Create(aOwner: TComponent); +begin + inherited Create(aOwner); + EnsureElement; +end; + +class function TViewPort.Instance: TViewPort; +begin + if Finstance=Nil then + FInstance:=TViewPort.Create(Nil); + Result:=FInstance; +end; + +{ TButtonWidget } + +{ TButtonWidget } + +procedure TButtonWidget.SetText(AValue: String); + +begin + if FText=AValue then Exit; + FText:=AValue; + if IsRendered then + Element.InnerText:=Ftext; +end; + + +procedure TButtonWidget.SetName(const NewName: TComponentName); + +Var + Old : String; + +begin + Old:=Name; + inherited SetName(NewName); + if (FText=Old) and (csDesigning in ComponentState) then + FText:=NewName; +end; + +function TButtonWidget.HTMLTag: String; +begin + Result:='button'; +end; + +procedure TButtonWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); +begin + Inherited; + aElement.InnerText:=Text; +end; + +procedure TButtonWidget.Click; + +begin + DispatchEvent('click'); +end; + + +{ TCustomInputWidget } + +function TCustomInputWidget.GetValue: String; + +Var + Inp : TJSHTMLInputElement; +begin + Inp:=InputElement; + If Assigned(Inp) then + Result:=Inp.value + else + Result:=FValue +end; + +function TCustomInputWidget.GetText: String; +Var + Inp : TJSHTMLElement; + +begin + Inp:=Element; + If Assigned(Inp) then + Result:=Inp.InnerText + else + Result:=FText; +end; + +function TCustomInputWidget.GetReadOnly: Boolean; +begin + if IsElementDirty then + FReadonly:=InputElement.readOnly; + Result:=FReadonly; +end; + +function TCustomInputWidget.GetRequired: Boolean; +begin + if IsElementDirty then + FRequired:=InputElement.Required; + Result:=FRequired; +end; + +function TCustomInputWidget.GetValueName: String; + +Var + Inp : TJSHTMLInputElement; + +begin + Inp:=InputElement; + If Assigned(Inp) then + Result:=Inp.Name + else + begin + Result:=FValueName; + if Result='' then + Result:=Name; + end; +end; + +procedure TCustomInputWidget.SetReadonly(AValue: Boolean); +begin + If aValue=ReadOnly then exit; + FReadOnly:=aValue; + if IsRendered then + InputElement.Readonly:=FReadOnly; +end; + +procedure TCustomInputWidget.SetRequired(AValue: Boolean); +begin + If aValue=Required then exit; + FRequired:=aValue; + if IsRendered then + InputElement.Required:=FRequired; +end; + +procedure TCustomInputWidget.SetText(AValue: String); +Var + Inp : TJSHTMLElement; + +begin + if aValue=Text then exit; + FText:=aValue; + Inp:=Element; + If Assigned(Inp) then + Inp.innerText:=aValue; +end; + +procedure TCustomInputWidget.SetValue(AValue: String); + +Var + Inp : TJSHTMLInputElement; + +begin + if aValue=Value then exit; + FValue:=aValue; + Inp:=InputElement; + If Assigned(Inp) then + Inp.value:=aValue; +end; + +procedure TCustomInputWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); + +var + Inp : TJSHTMLInputElement absolute aElement; + +begin + Inherited; + if (ExternalElement) and (FValue='') then + FValue:=TJSHTMLInputElement(aElement).value + else + begin + Inp._type:=InputType; + Inp.name:=FValueName; + Inp.value:=FValue; + Inp.Required:=FRequired; + Inp.ReadOnly:=FReadOnly; + end; +end; + +function TCustomInputWidget.HTMLTag: String; +begin + Result:='input'; +end; + +function TCustomInputWidget.GetInputElement: TJSHTMLInputElement; +begin + Result:=TJSHTMLInputElement(Element); +end; + +procedure TCustomInputWidget.SetValueName(AValue: String); +Var + Inp : TJSHTMLInputElement; +begin + if aValue=ValueName then exit; + FValueName:=aValue; + Inp:=InputElement; + If Assigned(Inp) then + Inp.name:=aValue; +end; + +procedure TCustomInputWidget.SetName(const NewName: TComponentName); + +Var + Old : String; + +begin + Old:=Name; + inherited SetName(NewName); + if (Value=Old) then + Value:=NewName; +end; + +end. + diff --git a/packages/webwidget/lazwebwidgets.lpk b/packages/webwidget/lazwebwidgets.lpk new file mode 100644 index 0000000..162d027 --- /dev/null +++ b/packages/webwidget/lazwebwidgets.lpk @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <Package Version="4"> + <PathDelim Value="\"/> + <Name Value="lazwebwidgets"/> + <Type Value="RunTimeOnly"/> + <AutoUpdate Value="Manually"/> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <SearchPaths> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Other> + <ExecuteBefore> + <Command Value="$MakeExe(IDE,pas2js) -O- -Jc -vbq lazwebwidgets.pas"/> + <Parsers Count="1"> + <Item1 Value="Pas2JS"/> + </Parsers> + </ExecuteBefore> + </Other> + <SkipCompiler Value="True"/> + </CompilerOptions> + <Files Count="2"> + <Item1> + <Filename Value="webwidget.pas"/> + <UnitName Value="webwidget"/> + </Item1> + <Item2> + <Filename Value="htmlwidgets.pp"/> + <UnitName Value="htmlwidgets"/> + </Item2> + </Files> + <RequiredPkgs Count="2"> + <Item1> + <PackageName Value="pas2js_rtl"/> + </Item1> + <Item2> + <PackageName Value="FCL"/> + </Item2> + </RequiredPkgs> + <UsageOptions> + <UnitPath Value="$(PkgOutDir)"/> + </UsageOptions> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + </Package> +</CONFIG> diff --git a/packages/webwidget/lazwebwidgets.pas b/packages/webwidget/lazwebwidgets.pas new file mode 100644 index 0000000..a26f92f --- /dev/null +++ b/packages/webwidget/lazwebwidgets.pas @@ -0,0 +1,15 @@ +{ This file was automatically created by Lazarus. Do not edit! + This source is only used to compile and install the package. + } + +unit lazwebwidgets; + +{$warn 5023 off : no warning about unused units} +interface + +uses + webwidget; + +implementation + +end. diff --git a/packages/webwidget/tests/btnrun.pp b/packages/webwidget/tests/btnrun.pp new file mode 100644 index 0000000..ab79b59 --- /dev/null +++ b/packages/webwidget/tests/btnrun.pp @@ -0,0 +1,42 @@ +unit btnrun; + +{$mode objfpc} + +interface + +uses + Classes, fpcunitreport, BrowserConsole, web; + +Type + + { TConsoleRunner } + + TConsoleRunner = Class(TRunForm) + Private + FRun : TJSHTMLButtonElement; + function DoRunTest(aEvent: TJSMouseEvent): boolean; + public + procedure initialize; override; + end; + +implementation + +{ TConsoleRunner } + +function TConsoleRunner.DoRunTest(aEvent: TJSMouseEvent): boolean; +begin + Result:=False; + ResetConsole; + If Assigned(OnRun) then + OnRun(Self); +end; + +procedure TConsoleRunner.initialize; +begin + FRun:=TJSHTMLButtonElement(document.getElementById('RunTest')); + FRun.onClick:=@DoRunTest; + ResetConsole; +end; + +end. + diff --git a/packages/webwidget/tests/tchtmlwidgets.pp b/packages/webwidget/tests/tchtmlwidgets.pp new file mode 100644 index 0000000..4bf6764 --- /dev/null +++ b/packages/webwidget/tests/tchtmlwidgets.pp @@ -0,0 +1,1155 @@ +unit tcHTMLWidgets; + +{$mode objfpc} + +interface + +uses + Classes, SysUtils, fpcunit, testregistry, web, webwidget, htmlwidgets, tcwidget; + +Type + { TTestButtonWidget } + + TTestButtonWidget = Class(TBaseTestWidget) + private + FButton: TButtonWidget; + Protected + Procedure SetUp; override; + Procedure TearDown; override; + Property Button : TButtonWidget Read FButton; + Published + Procedure TestTextBeforeRender; + Procedure TestTextAfterRender; + Procedure TestTextElementID; + Procedure TestClick; + end; + + { TTestLabelWidget } + TMyLabelWidget = Class(TLabelWidget) + Public + Property LabelElement; + end; + + TTestLabelWidget = Class(TBaseTestWidget) + private + FEdit: TTextInputWidget; + FMy: TMyLabelWidget; + Protected + Procedure SetUp; override; + Procedure TearDown; override; + Property My : TMyLabelWidget Read FMy; + Property Edit : TTextInputWidget Read FEdit; + Published + Procedure TestPropsBeforeRender; + Procedure TestPropsAfterRender; + end; + + { TTestViewPort } + + { TMyViewPort } + + TMyViewPort = Class(TViewPort) + Public + Procedure SetParentId; + Procedure SetParent; + Procedure SetElementID; + end; + + TTestViewPort = Class(TBaseTestWidget) + private + FMy: TMyViewPort; + Protected + Procedure Setup; override; + Procedure TearDown; override; + Property My : TMyViewPort Read FMy; + Published + Procedure TestInstance; + Procedure TestHTMLTag; + Procedure TestElement; + Procedure TestUnrender; + Procedure TestNoParent; + Procedure TestNoElementID; + Procedure TestNoParentID; + end; + + { TTestPage } + + { TMyWebPage } + + TMyWebPage = Class(TWebPage) + Public + Procedure SetParentId; + Procedure SetParent; + Procedure SetElementID; + end; + + TTestPage = Class(TBaseTestWidget) + private + FMy: TMyWebPage; + Protected + Function CreateElement(aID : String) : TJSHTMLElement; + Procedure Setup; override; + Procedure TearDown; override; + Property My : TMyWebPage Read FMy; + Published + Procedure TestEmpty; + Procedure TestAsWindow; + Procedure TestNoParentOK; + Procedure TestDefaultTag; + end; + + { TBaseTestInputElement } + + TInputHack = class(TCustomInputWidget) + Public + Property Element; + Property InputElement; + end; + + TBaseTestInputElement = Class(TBaseTestWidget) + private + FMy: TCustomInputWidget; + function GetInputElement: TJSHTMLInputElement; + Protected + // Must be handled in descendent. Called during setup to populate My. + Function CreateInput : TCustomInputWidget; virtual; abstract; + // (Re)create my. Calls createinput + Procedure CreateMy; virtual; + Procedure Setup; override; + Procedure TearDown; override; + // Assert basic properties are correct on the element. + procedure AssertBaseProps(aType, aValueName, aValue: String; aText: String=''); + Property My : TCustomInputWidget Read FMy; + Property InputElement : TJSHTMLInputElement Read GetInputElement; + Published + Procedure TestEmpty; + Procedure TestRequiredOnRender; + Procedure TestReadOnlyOnRender; + Procedure TestRequiredAfterRender; + Procedure TestReadOnlyAfterRender; + end; + + { TTestTextInputElement } + + TTestTextInputElement = Class(TBaseTestInputElement) + Protected + FITT: TInputTextType; + Procedure setup; override; + Function CreateInput : TCustomInputWidget; override; + Function MyText : TTextInputWidget; + Published + Procedure TestDefaultTextType; + Procedure TestRender; + Procedure TestChangeValue; + Procedure TestChangeName; + Procedure TestChangeTextType; + Procedure TestTypePassword; + Procedure TestTypeNumber; + Procedure TestAsNumber; + Procedure TestTypeEmail; + Procedure TestTypeSearch; + Procedure TestTypeTel; + Procedure TestTypeURL; + Procedure TestTypeColor; + end; + + { TTestRadioInputElement } + + TTestRadioInputElement = Class(TBaseTestInputElement) + Protected + Function CreateInput : TCustomInputWidget; override; + Function MyRadio : TRadioInputWidget; + Published + Procedure TestPropsOnRender; + Procedure TestPropsAfterRender; + end; + + TTestCheckboxInputElement = Class(TBaseTestInputElement) + Protected + Function CreateInput : TCustomInputWidget; override; + Function MyCheckbox : TCheckboxInputWidget; + Published + Procedure TestPropsOnRender; + Procedure TestPropsAfterRender; + end; + + TMyDateInputWidget = Class(TDateInputWidget) + end; + + { TTestDateInputElement } + + TTestDateInputElement = Class(TBaseTestInputElement) + Protected + Function CreateInput : TCustomInputWidget; override; + Procedure CreateMy; override; + Function MyDate : TMyDateInputWidget; + Published + Procedure TestPropsOnRender; + Procedure TestPropsAfterRender; + end; + + TMyFileInputWidget = Class(TFileInputWidget) + end; + + { TTestFileInputElement } + + TTestFileInputElement = Class(TBaseTestInputElement) + Protected + Function CreateInput : TCustomInputWidget; override; + Procedure CreateMy; override; + Function MyFile : TMyFileInputWidget; + Published + Procedure TestPropsOnRender; + Procedure TestPropsAfterRender; + end; + + TMyHiddenInputWidget = Class(THiddenInputWidget) + end; + + { TTestHiddenInputElement } + + TTestHiddenInputElement = Class(TBaseTestInputElement) + Protected + Function CreateInput : TCustomInputWidget; override; + Function MyHidden : TMyHiddenInputWidget; + Published + Procedure TestPropsOnRender; + Procedure TestPropsAfterRender; + end; + + { TTestTextAreaElement } + TMyTextAreaWidget = Class(TTextAreaWidget) + Public + Property TextArea; + end; + + TTestTextAreaElement = Class(TBaseTestWidget) + private + FMy: TMyTextAreaWidget; + function GetArea: TJSHTMLTextAreaElement; + Protected + Procedure Setup; override; + Procedure TearDown; override; + Property My : TMyTextAreaWidget Read FMy; + Property Area : TJSHTMLTextAreaElement Read GetArea; + Published + Procedure TestEmpty; + Procedure TestPropsOnRender; + Procedure TestPropsAfterRender; + end; + + TMyImageWidget = Class(TImageWidget) + Public + Property Element; + end; + + { TTestImageElement } + + TTestImageElement = Class(TBaseTestWidget) + private + FMy: TMyImageWidget; + function GetImg: TJSHTMLImageElement; + Protected + Procedure Setup; override; + Procedure TearDown; override; + Function ThisURL : String; + Property My : TMyImageWidget Read FMy; + Property Image : TJSHTMLImageElement Read GetImg; + Published + Procedure TestEmpty; + Procedure TestPropsOnRender; + Procedure TestPropsAfterRender; + end; + + TMySelectWidget = Class(TSelectWidget) + Public + Property Element; + Property SelectElement; + Property Options; + end; + + { TTestSelectElement } + + TTestSelectElement = Class(TBaseTestWidget) + private + FMy: TMySelectWidget; + procedure AssertOption(Idx: Integer; aText, aValue: String; Selected: Boolean=False); + function GetOptions: TJSHTMLOPtionElementArray; + function GetSelect: TJSHTMLSelectElement; + Protected + Procedure Setup; override; + Procedure TearDown; override; + Property My : TMySelectWidget Read FMy; + Property Select : TJSHTMLSelectElement Read GetSelect; + Property Options : TJSHTMLOPtionElementArray Read GetOptions; + Published + Procedure TestEmpty; + Procedure TestPropsOnRender; + Procedure TestPropsAfterRender; + Procedure TestMultiSelect; + end; + + +implementation + +{ TTestLabelWidget } + +procedure TTestLabelWidget.SetUp; +begin + inherited SetUp; + FMy:=TMyLabelWidget.Create(Nil); + My.Text:='Your name'; + My.ParentID:=SBaseWindowID; + FEdit:=TTextInputWidget.Create(Nil); + FEdit.ParentID:=SBaseWindowID; + FMy.LabelFor:=Edit; +end; + +procedure TTestLabelWidget.TearDown; +begin + FreeAndNil(Fmy); + FreeAndNil(FEdit); + inherited TearDown; +end; + +procedure TTestLabelWidget.TestPropsBeforeRender; +begin + Edit.Refresh; + My.Refresh; + AssertEquals('text','Your name',My.LabelElement.innerText); + AssertEquals('for',Edit.ElementID,My.LabelElement.For_); +end; + +procedure TTestLabelWidget.TestPropsAfterRender; +begin + My.LabelFor:=Nil; + My.Refresh; + AssertEquals('text','Your name',My.LabelElement.innerText); + AssertEquals('for','',My.LabelElement.For_); + // Will render Edit! + My.LabelFor:=Edit; + AssertTrue('Have edit id',Edit.ElementID<>''); + My.Text:='My Name'; + My.Refresh; + AssertEquals('text','My Name',My.LabelElement.innerText); + AssertEquals('for',Edit.ElementID,My.LabelElement.For_); +end; + + +{ TTestSelectElement } + +function TTestSelectElement.GetOptions: TJSHTMLOPtionElementArray; +begin + Result:=My.Options; +end; + +function TTestSelectElement.GetSelect: TJSHTMLSelectElement; +begin + Result:=My.SelectElement; +end; + +procedure TTestSelectElement.Setup; +begin + inherited Setup; + FMy:=TMySelectWidget.Create(Nil); + FMy.ParentID:=SBaseWindowID; + FMy.Items.Add('One'); + FMy.Items.Add('Two'); + FMy.Items.Add('Three'); + FMy.Values.Add('1'); + FMy.Values.Add('2'); + FMy.Values.Add('3'); + FMy.SelectedIndex:=0; +end; + +procedure TTestSelectElement.TearDown; +begin + FreeAndNil(FMy); + inherited TearDown; +end; + + +procedure TTestSelectElement.TestEmpty; +begin + AssertNotNull('Have widget',My); + AssertNull('Not rendered',My.Element); +end; + +procedure TTestSelectElement.AssertOption(Idx : Integer; aText,aValue : String; Selected : Boolean= False); + +Var + O : TJSHTMLOptionElement; + +begin + AssertTrue('Correct index',Idx<Select.childElementCount); + O:=Select.children[Idx] as TJSHTMLOptionElement; + AssertEquals('Text',aText,O.InnerText); + if aValue='' then aValue:=aText; + AssertEquals('Value',aValue,O.Value); + AssertEquals('Selected',Selected,O.selected); +end; + +procedure TTestSelectElement.TestPropsOnRender; + + +begin + My.Refresh; + AssertTree('select/option'); + AssertEquals('Multi',False,Select.multiple); + AssertEquals('SelectedIndex',0,Select.selectedIndex); + AssertEquals('Amount of options',3,Length(Options)); + AssertEquals('Amount of option values',3,Select.childElementCount); + AssertOption(0,'One','1',True); + AssertOption(1,'Two','2'); + AssertOption(2,'Three','3'); +end; + +procedure TTestSelectElement.TestPropsAfterRender; + +Var + L1,L2 : TStrings; + +begin + TestPropsOnRender; + My.Multiple:=True; + L1:=My.Items; + l2:=My.Values; + L1.BeginUpdate; + L2.BeginUpdate; + L1.Clear; + L1.Add('Alpha'); + L1.Add('Beta'); + L1.Add('Gamma'); + L2.Clear; + L2.Add('a'); + L2.Add('b'); + L1.EndUpdate; + L2.EndUpdate; + My.SelectedIndex:=2; + AssertEquals('Multi',True,Select.multiple); + AssertEquals('SelectedIndex',2,Select.selectedIndex); + AssertEquals('Amount of options',3,Length(Options)); + AssertEquals('Amount of option values',3,Select.childElementCount); + AssertOption(0,'Alpha','a'); + AssertOption(1,'Beta','b'); + AssertOption(2,'Gamma','Gamma',True); +end; + +procedure TTestSelectElement.TestMultiSelect; + +Var + I : Integer; + +begin + TestPropsOnRender; + My.Multiple:=True; + For I:=0 to My.Items.Count-1 do + begin + AssertEquals(IntToStr(I)+' selected',I=My.SelectedIndex,My.Selected[I]); + AssertEquals(IntToStr(I)+' option selected',I=My.SelectedIndex,Options[i].Selected); + end; + My.Selected[2]:=True; + AssertEquals('First selected index',0,My.SelectedIndex); + AssertEquals('Additional selected',True,Options[2].Selected); + AssertEquals('Additional option selected',True,My.Selected[2]); + AssertEquals('SelectionCount',2,My.selectionCount); + AssertEquals('SelectionValue[0]','1',My.selectionValue[0]); + AssertEquals('SelectionItem[0]','One',My.SelectionItem[0]); + AssertEquals('SelectionValue[1]','3',My.selectionValue[1]); + AssertEquals('SelectionItem[1]','Three',My.selectionItem[1]); + +end; + +{ TTestImageElement } + +function TTestImageElement.GetImg: TJSHTMLImageElement; +begin + Result:=TJSHTMLImageElement(My.Element); +end; + +procedure TTestImageElement.Setup; +begin + inherited Setup; + FMy:=TMyImageWidget.Create(Nil); + FMy.ParentID:=SBaseWindowID; + FMy.Src:='img.png'; + FMy.Width:=64; + FMy.Height:=128; +end; + +procedure TTestImageElement.TearDown; +begin + FreeAndNil(FMy); + inherited TearDown; +end; + +function TTestImageElement.ThisURL: String; +begin + Result:=ExtractFilePath(Window.Location.href); +end; + +procedure TTestImageElement.TestEmpty; +begin + AssertNotNull('have image',My); + AssertNull('Not rendered',My.Element); +end; + +procedure TTestImageElement.TestPropsOnRender; +begin + My.Refresh; + AssertNotNull('have element',My.Element); + AssertEquals('URL',ThisURL+'img.png',Image.src); + AssertEquals('Width',64,Image.width); + AssertEquals('Height',128,Image.Height); +end; + +procedure TTestImageElement.TestPropsAfterRender; +begin + My.Refresh; + My.Src:='img2.png'; + My.Width:=88; + My.Height:=166; + AssertEquals('URL',ThisURL+'img2.png',Image.src); + AssertEquals('Width',88,Image.width); + AssertEquals('Height',166,Image.Height); +end; + +{ TTestHiddenInputElement } + +function TTestHiddenInputElement.CreateInput: TCustomInputWidget; +begin + Result:=THiddenInputWidget.Create(Nil); +end; + +function TTestHiddenInputElement.MyHidden: TMyHiddenInputWidget; +begin + Result:=My as TMyHiddenInputWidget; +end; + + +procedure TTestHiddenInputElement.TestPropsOnRender; +begin + My.Refresh; + AssertBaseProps('','',''); +end; + +procedure TTestHiddenInputElement.TestPropsAfterRender; +begin + My.Refresh; + My.ValueName:='a'; + My.Value:='b'; + AssertBaseProps('hidden','a','b'); +end; + +{ TTestDateInputElement } + +function TTestDateInputElement.CreateInput: TCustomInputWidget; +begin + Result:=TMyDateInputWidget.Create(Nil); +end; + +procedure TTestDateInputElement.CreateMy; +begin + inherited CreateMy; + MyDate.Date:=Date; +end; + +function TTestDateInputElement.MyDate: TMyDateInputWidget; +begin + Result:=My as TMyDateInputWidget; +end; + +procedure TTestDateInputElement.TestPropsOnRender; +begin + My.Refresh; + AssertBaseProps('','',FormatDateTime('yyyy-mm-dd',Date)); +end; + +procedure TTestDateInputElement.TestPropsAfterRender; +begin + My.Refresh; + MyDate.Date:=Date-1; + AssertBaseProps('','',FormatDateTime('yyyy-mm-dd',Date-1)); +end; + +{ TTestFileInputElement } + +function TTestFileInputElement.CreateInput: TCustomInputWidget; +begin + Result:=TMyFileInputWidget.Create(Nil); +end; + +procedure TTestFileInputElement.CreateMy; +begin + inherited CreateMy; + My.Value:=''; +end; + +function TTestFileInputElement.MyFile: TMyFileInputWidget; +begin + Result:=My as TMyFileInputWidget; +end; + +procedure TTestFileInputElement.TestPropsOnRender; +begin + My.Refresh; + // We cannot use assertbaseprops + AssertTree('input('+My.ElementID+')'); + AssertEquals('Type','file',InputElement._Type); + AssertEquals('Value name','Test',InputElement.name); + AssertEquals('Value','',InputElement.value); + AssertEquals('Text (inner text)','',InputElement.innerText); +end; + +procedure TTestFileInputElement.TestPropsAfterRender; +begin + My.Refresh; + // We cannot use assertbaseprops + AssertTree('input('+My.ElementID+')'); + AssertEquals('Type','file',InputElement._Type); + AssertEquals('Value name','Test',InputElement.name); + AssertEquals('Value','',InputElement.value); + AssertEquals('Text (inner text)','',InputElement.innerText); +end; + + +{ TTestRadioInputElement } + +function TTestRadioInputElement.CreateInput: TCustomInputWidget; +begin + Result:=TRadioInputWidget.Create(Nil); +end; + +function TTestRadioInputElement.MyRadio: TRadioInputWidget; +begin + Result:=My as TRadioInputWidget; +end; + +procedure TTestRadioInputElement.TestPropsOnRender; +begin + MyRadio.Checked:=true; + My.Refresh; + AssertBaseProps('','',''); + AssertEquals('Checked',true,InputElement.Checked); +end; + +procedure TTestRadioInputElement.TestPropsAfterRender; +begin + My.Refresh; + AssertEquals('Checked before',False,InputElement.Checked); + MyRadio.Checked:=true; + AssertBaseProps('','',''); + AssertEquals('Checked after',true,InputElement.Checked); +end; + +{ TTestCheckBoxInputElement } + +function TTestCheckBoxInputElement.CreateInput: TCustomInputWidget; +begin + Result:=TCheckBoxInputWidget.Create(Nil); +end; + +function TTestCheckBoxInputElement.MyCheckBox: TCheckBoxInputWidget; +begin + Result:=My as TCheckBoxInputWidget; +end; + +procedure TTestCheckBoxInputElement.TestPropsOnRender; +begin + MyCheckBox.Checked:=true; + My.Refresh; + AssertBaseProps('','',''); + AssertEquals('Checked',true,InputElement.Checked); +end; + +procedure TTestCheckBoxInputElement.TestPropsAfterRender; +begin + My.Refresh; + AssertEquals('Checked before',False,InputElement.Checked); + MyCheckBox.Checked:=true; + AssertBaseProps('','',''); + AssertEquals('Checked after',true,InputElement.Checked); +end; + +{ TTestTextAreaElement } + +function TTestTextAreaElement.GetArea: TJSHTMLTextAreaElement; +begin + Result:=FMy.TextArea +end; + +procedure TTestTextAreaElement.Setup; +begin + inherited Setup; + FMy:=TMyTextAreaWidget.Create(Nil); + FMy.Lines.Add('a'); + FMy.Lines.Add('b'); +end; + +procedure TTestTextAreaElement.TearDown; +begin + FreeAndNil(FMy); + inherited TearDown; +end; + +procedure TTestTextAreaElement.TestEmpty; +begin + AssertNotNull(My); +end; + +procedure TTestTextAreaElement.TestPropsOnRender; +begin + My.ParentID:=BaseID; + My.ValueName:='test'; + My.Columns:=25; + My.Rows:=35; + My.MaxLength:=500; + My.Wrap:=tawHard; + My.Required:=True; + My.ReadOnly:=True; + My.Refresh; + AssertEquals('ValueName','test',area.Name); + AssertEquals('Wrap','hard',area.Wrap); + AssertEquals('Rows',35,area.Rows); + AssertEquals('Cols',25,area.Cols); + AssertEquals('MaxLength',500,area.MaxLength); + AssertEquals('Text','a'+sLineBreak+'b'+sLineBreak,area.innerHtml); + AssertEquals('Required',true,Area.Required); + AssertEquals('ReadOnly',true,Area.ReadOnly); +end; + +procedure TTestTextAreaElement.TestPropsAfterRender; + +begin + My.ParentID:=BaseID; + My.Refresh; + My.ValueName:='test'; + My.Columns:=25; + My.Rows:=35; + My.MaxLength:=500; + My.Required:=True; + My.ReadOnly:=True; + My.Wrap:=tawHard; + With My.Lines do + begin + BeginUpdate; + Clear; + Add('d'); + Add('e'); + EndUpdate; + end; + AssertEquals('ValueName','test',area.Name); + AssertEquals('Wrap','hard',area.Wrap); + AssertEquals('Rows',35,area.Rows); + AssertEquals('Cols',25,area.Cols); + AssertEquals('MaxLength',500,area.MaxLength); + AssertEquals('Text','d'+sLineBreak+'e'+sLineBreak,area.innerHTML); + AssertEquals('Required',true,Area.Required); + AssertEquals('ReadOnly',true,Area.ReadOnly); +end; + +{ TTestTextInputElement } + +procedure TTestTextInputElement.setup; +begin + inherited setup; + FITT:=ittText; +end; + +function TTestTextInputElement.CreateInput: TCustomInputWidget; +begin + Result:=TTextInputWidget.Create(Nil); + TTextInputWidget(Result).TextType:=FITT; +end; + +function TTestTextInputElement.MyText: TTextInputWidget; +begin + Result:=My as TTextInputWidget; +end; + +procedure TTestTextInputElement.TestDefaultTextType; +begin + AssertTrue('Correct type',ittText=MyText.TextType); +end; + +procedure TTestTextInputElement.TestRender; +begin + My.Refresh; + AssertBaseProps('','','',''); +end; + +procedure TTestTextInputElement.TestChangeValue; +begin + My.Refresh; + AssertBaseProps('','','',''); + My.Value:='soso'; + AssertEquals('Value propagates','soso',InputElement.value); +end; + +procedure TTestTextInputElement.TestChangeName; +begin + My.Refresh; + AssertBaseProps('','','',''); + My.ValueName:='soso'; + AssertEquals('ValueName propagates','soso',InputElement.name); +end; + +procedure TTestTextInputElement.TestChangeTextType; +begin + My.Refresh; + AssertBaseProps('','','',''); + MyText.TextType:=ittPassword; + AssertEquals('TextType propagates to type','password',InputElement._type); +end; + +procedure TTestTextInputElement.TestTypePassword; +begin + FItt:=ittPassword; + CreateMy; + My.Refresh; + AssertBaseProps('password','','',''); +end; + +procedure TTestTextInputElement.TestTypeNumber; +begin + FItt:=ittNumber; + CreateMy; + My.Refresh; + AssertBaseProps('number','','',''); +end; + +procedure TTestTextInputElement.TestAsNumber; +begin + TestTypeNumber; + AssertBaseProps('number','','',''); + AssertEquals('Correct read',1,MyText.AsNumber); + MyText.AsNumber:=123; + AssertEquals('Correctly set','123',InputElement.Value); + AssertEquals('Correctly set 2','123',Mytext.Value); +end; + +procedure TTestTextInputElement.TestTypeEmail; +begin + FItt:=ittEmail; + CreateMy; + My.Refresh; + AssertBaseProps('email','','',''); +end; + +procedure TTestTextInputElement.TestTypeSearch; +begin + FItt:=ittSearch; + CreateMy; + My.Refresh; + AssertBaseProps('search','','',''); +end; + +procedure TTestTextInputElement.TestTypeTel; +begin + FItt:=ittTelephone; + CreateMy; + My.Refresh; + AssertBaseProps('tel','','',''); +end; + +procedure TTestTextInputElement.TestTypeURL; +begin + FItt:=ittURL; + CreateMy; + My.Refresh; + AssertBaseProps('url','','',''); +end; + +procedure TTestTextInputElement.TestTypeColor; +begin + FItt:=ittColor; + CreateMy; + My.Refresh; + AssertBaseProps('color','','#000000',''); +end; + +{ TBaseTestInputElement } + +function TBaseTestInputElement.GetInputElement: TJSHTMLInputElement; +begin + Result:= TInputHack(My).InputElement; + AssertNotNull('Have input element',Result); +end; + +procedure TBaseTestInputElement.CreateMy; +begin + FreeAndNil(FMy); + FMy:=CreateInput; + FMy.ParentID:=BaseID; + FMy.ValueName:='Test'; + FMy.Value:='1'; +end; + +procedure TBaseTestInputElement.Setup; +begin + inherited Setup; + CreateMy; +end; + +procedure TBaseTestInputElement.TearDown; +begin + FreeAndNil(FMy); + inherited TearDown; +end; + + +procedure TBaseTestInputElement.AssertBaseProps(aType, aValueName, aValue : String; aText : String = ''); +Var + El : TJSHTMLInputElement; + +begin + if AType='' then + aType:=My.InputType; + if aValueName='' then + aValueName:='Test'; // Same as in CreateMy + if aValue='' then + aValue:='1'; // Same as in CreateMy + el:=InputElement; + AssertTree('input('+el.ID+')'); + AssertEquals('Type',aType,el._Type); + AssertEquals('Value name',aValueName,el.name); + AssertEquals('Value',aValue,el.value); + AssertEquals('Text (inner text)',aText,el.innerText); +end; + +procedure TBaseTestInputElement.TestEmpty; +begin + AssertNotNull('Have element',My); +end; + +procedure TBaseTestInputElement.TestRequiredOnRender; +begin + My.Required:=True; + My.Refresh; + AssertEquals('required',True,InputElement.required); +end; + +procedure TBaseTestInputElement.TestReadOnlyOnRender; +begin + My.ReadOnly:=True; + My.Refresh; + AssertEquals('ReadOnly',True,InputElement.ReadOnly); +end; + +procedure TBaseTestInputElement.TestRequiredAfterRender; +begin + My.Refresh; + My.Required:=True; + AssertEquals('required',True,InputElement.required); +end; + +procedure TBaseTestInputElement.TestReadOnlyAfterRender; +begin + My.Refresh; + My.ReadOnly:=True; + AssertEquals('ReadOnly',True,InputElement.ReadOnly); +end; + +{ TMyWebPage } + +procedure TMyWebPage.SetParentId; +begin + ParentID:='A'; +end; + +procedure TMyWebPage.SetParent; +begin + Parent:=TViewPort.Create(Nil); +end; + +procedure TMyWebPage.SetElementID; +begin + ElementID:=BaseID; +end; + +{ TTestPage } + +function TTestPage.CreateElement(aID: String): TJSHTMLElement; +begin + Result:=TJSHTMLElement(Document.CreateElement('div')); + Result.ID:=aID; + BaseWindow.AppendChild(Result); +end; + +procedure TTestPage.Setup; +begin + inherited Setup; + FMy:=TMyWebPage.Create(Nil); +end; + +procedure TTestPage.TearDown; +begin + FreeAndNil(FMy); + inherited TearDown; +end; + +procedure TTestPage.TestEmpty; +begin + AssertNotNull('Have element'); +end; + +procedure TTestPage.TestAsWindow; +begin + // Set element to base-window + My.SetElementID; + AssertSame('Correct',BaseWindow,My.Element); +end; + +procedure TTestPage.TestNoParentOK; +begin + My.Refresh; + AssertSame('Correct parent',ViewPort.Element,My.ParentElement); +end; + +procedure TTestPage.TestDefaultTag; +begin + AssertEquals('Correct tag','div',My.HTMLTag); +end; + +{ TMyViewPort } + +procedure TMyViewPort.SetParentId; +begin + ParentID:='SomeThing'; +end; + +procedure TMyViewPort.SetParent; +begin + Parent:=Instance +end; + +procedure TMyViewPort.SetElementID; +begin + ElementID:='Something'; +end; + +{ TTestViewPort } + +procedure TTestViewPort.Setup; +begin + inherited Setup; + FMy:=TMyViewPort.Create(Nil); +end; + +procedure TTestViewPort.TearDown; +begin + FreeAndNil(FMy); + inherited TearDown; +end; + +procedure TTestViewPort.TestInstance; + +begin + AssertNotNull('Have viewport',ViewPort); + AssertSame('Have viewport',TViewPort.Instance,ViewPort); +end; + +procedure TTestViewPort.TestHTMLTag; +begin + AssertEquals('Correct tag','body',ViewPort.HTMLTag); +end; + +procedure TTestViewPort.TestElement; +begin + AssertSame('Correct Element',Document.Body,ViewPort.Element); +end; + +procedure TTestViewPort.TestUnrender; +begin + AssertSame('Element retained',Document.Body,ViewPort.Element); +end; + +procedure TTestViewPort.TestNoParent; +begin + AssertException('No parent can be set',EWidgets,@My.SetParent); +end; + +procedure TTestViewPort.TestNoElementID; +begin + AssertException('No elementID can be set',EWidgets,@My.SetElementID); +end; + +procedure TTestViewPort.TestNoParentID; +begin + AssertException('No ParentID can be set',EWidgets,@My.SetParentID); +end; + +{ TTestButtonWidget } + +procedure TTestButtonWidget.SetUp; +begin + inherited SetUp; + FButton:=TButtonWidget.Create(Nil); +end; + +procedure TTestButtonWidget.TearDown; +begin + FreeAndNil(FButton); + inherited TearDown; +end; + +procedure TTestButtonWidget.TestTextBeforeRender; + +Var + El : TJSHTMLElement; + +begin + Button.ParentID:=BaseID; + Button.Text:='Click me'; + Button.Refresh; + El:=AssertTree('button('+Button.ElementID+')'); + AssertEquals('Text set','Click me',el.innerText); +end; + +procedure TTestButtonWidget.TestTextAfterRender; +Var + El : TJSHTMLElement; + +begin + Button.ParentID:=BaseID; + Button.Refresh; + El:=AssertTree('button('+Button.ElementID+')'); + Button.Text:='Click me'; + AssertEquals('Text set','Click me',el.innerText); +end; + +procedure TTestButtonWidget.TestTextElementID; +Var + El : TJSHTMLElement; + +begin + el:=TJSHTMLElement(Document.createElement('button')); + el.id:='b1'; + BaseWindow.appendChild(el); + El:=AssertTree('button(b1)'); + Button.elementID:='b1'; + Button.Refresh; + Button.Text:='Click me'; + AssertEquals('Text set','Click me',el.innerText); +end; + +procedure TTestButtonWidget.TestClick; + +begin + Button.ParentID:=BaseID; + Button.Refresh; + Button.OnClick:=@MyTestEventHandler; + Button.Click; + AssertEvent('click',Button); +end; + +initialization + RegisterTests([TTestViewPort,TTestButtonWidget,TTestPage, + TTestTextInputElement,TTestTextAreaElement, + TTestRadioInputElement,TTestCheckBoxInputElement, + TTestDateInputElement,TTestFileInputElement, + TTestHiddenInputElement, TTestImageElement, + TTestImageElement,TTestSelectElement, + TTestLabelWidget]); +end. + diff --git a/packages/webwidget/tests/tcwidget.pp b/packages/webwidget/tests/tcwidget.pp new file mode 100644 index 0000000..c6afd7f --- /dev/null +++ b/packages/webwidget/tests/tcwidget.pp @@ -0,0 +1,1760 @@ +unit tcWidget; + +{$mode objfpc} + +interface + +uses + Classes, SysUtils, fpcunit, testregistry, js, web, webwidget; + +Const + SBaseWindowID = 'widget-window'; // Must match what is in HTML File + BaseID = SBaseWindowID; + + SMyChildID = 'mychild'; + SMyParentID = 'myparent'; + +Type + TJSEventClass = Class of TJSEvent; + + { TMyWebWidget } + + TMyWebWidget = Class(TWebWidget) + private + FAdd: String; + Protected + Function WidgetClasses: String; override; + Function DoRenderHTML(aParent,aElement : TJSHTMLElement) :TJSHTMLElement; override; + Public + Function HTMLTag : String; override; + Function MyElement : TJSHTMLElement; + Function MyParent : TJSHTMLElement; + Function MyContent : TJSHTMLElement; + Function MyTop : TJSHTMLElement; + Property AddedClasses : String Read FAdd Write FAdd; + end; + TMyChildWidget = Class(TMyWebWidget); + TMyParentWidget = Class(TMyWebWidget); + + { TMySubContentWidget } + + TMySubContentWidget = Class(TMyParentWidget) + Private + FSub: TJSHTMLElement; + Public + Function DoRenderHTML(aParent,aElement : TJSHTMLElement) :TJSHTMLElement; override; + Procedure DoUnRender(aParent: TJSHTMLElement); override; + function GetContentElement: TJSHTMLELement; override; + end; + + { TMyPrefixSubContentWidget } + + TMyPrefixSubContentWidget = Class(TMySubContentWidget) + Private + FTop : TJSHTMLELement; + Public + Function DoRenderHTML(aParent,aElement : TJSHTMLElement) :TJSHTMLElement; override; + Procedure DoUnRender(aParent: TJSHTMLElement); override; + function GetTopElement: TJSHTMLELement; override; + end; + + { TMyRefWidget } + + TMyRefWidget = Class(TMyPrefixSubContentWidget) + Protected + FUL : TJSHTMLElement; + FSubs : TJSHTMLElementArray; + Function DoRenderHTML(aParent,aElement : TJSHTMLElement) :TJSHTMLElement; override; + Public + Property References; + Property Subs : TJSHTMLElementArray read FSubs; + Property UL : TJSHTMLElement read FUL; + end; + + + { TBaseTestWidget } + + TBaseTestWidget = Class(TTestCase) + private + FBaseWindow: TJSHTMLElement; + FEventCount : Integer; + FEventSender : TObject; + FEventName : String; + FDefaultPrevented : Boolean; + Protected + Procedure SetUp; override; + Procedure TearDown; override; + procedure MyTestEventHandler(Sender: TObject; Event: TJSEvent); + Procedure AssertEvent(aName : String; aElement : TCustomWebWidget = Nil); + Function FindElement(aID : String) : TJSHTMLElement; + Function GetElement(aID : String) : TJSHTMLElement; + // Checks for /tag(id)/tag(id) + Function AssertTree(aParent : TJSHTmlElement; aTree : String) :TJSHTMLElement; + Function AssertTree(aTree : String) : TJSHTMLElement; + Public + Property BaseWindow : TJSHTMLElement read FBaseWindow; + Property EventName : String Read FEventName; + // Set in MyTestEventHandler; + Property EventSender : TObject Read FEventSender; + Property EventCount : Integer Read FEventCount; + Property DefaultPrevented : Boolean read FDefaultPrevented; + end; + + { TTestWidgetBasicOperations } + + TTestWidgetBasicOperations = Class(TBaseTestWidget) + private + FMy: TMyChildWidget; + FMyParent: TMyParentWidget; + FMySub: TMySubContentWidget; + FMyTop: TMyPrefixSubContentWidget; + function GetMySub: TMySubContentWidget; + function GetTop: TMyPrefixSubContentWidget; + Protected + Procedure SetUp; override; + Procedure TearDown; override; + Function SetupElement : TJSHTMLElement; + procedure SetupParentElement; + // Check for event trigger + procedure TriggerEvent(aName: String; aClass: TJSEventClass=nil); + // Calls triggerevent and then testevent + procedure TestEvent(aName: String); + // Create parent element below windowbase + function CreateParentElement : TJSHTMLElement; + // Create child element below windowbase or below parent + function CreateMyElement(asChild : Boolean = False) : TJSHTMLELement; + // Set parent of child + Procedure SetParentElement; + // Set element ID of child + Procedure DoSetElementID; + // Set Parent ID of child + Procedure DoSetParentID; + // Set dataset element + Procedure DoSetDataset; + // Create parent element and bind parent widget to it. + function SetupParent: TJSHTMLElement; + Property MyWidget : TMyChildWidget Read FMy; + Property MyParentWidget : TMyParentWidget Read FMyParent; + Property MySubWidget : TMySubContentWidget Read GetMySub; + Property MyTopWidget : TMyPrefixSubContentWidget Read GetTop; + Published +// Public + Procedure TestEmpty; + Procedure TestNoElementIDAndParentElementID; + procedure TestNoParentElementIDAndElementID; + Procedure TestParentIDToElement; + Procedure TestElementIDToElement; + Procedure TestSetParent; + Procedure TestRenderParent; + Procedure TestRenderParentID; + Procedure TestUnRenderParent; + Procedure TestUnRenderParentID; + Procedure TestUnRenderElementID; + Procedure TestSubContent; + Procedure TestTopContent; + Procedure TestAddClasses; + Procedure TestAddClassesNormalized; + Procedure TestRemoveClasses; + Procedure TestRemoveClassesNormalized; + Procedure TestClassesBeforeRender; + Procedure TestClassesAfterRender; + Procedure TestWidgetClassesBeforeRender; + Procedure TestWidgetClassesAfterRender; + Procedure TestClassesOnElementID; + Procedure TestStylesBeforeRender; + Procedure TestStylesAfterRender; + Procedure TestStylesRefreshOnRender; + Procedure TestVisibleBeforeRender; + Procedure TestVisibleAfterRender; + Procedure TestVisibleBeforeRenderPreserves; + Procedure TestVisibleAfterRenderPreserves; + Procedure TestGetData; + Procedure TestSetData; + Procedure TestGetDataNotRendered; + Procedure TestSetDataNotRendered; + Procedure TestEventClick; + Procedure TestEventOnAbort; + Procedure TestEventOnAnimationCancel; + Procedure TestEventOnAnimationEnd; + Procedure TestEventOnAnimationIteration; + Procedure TestEventOnAnimationStart; + Procedure TestEventOnAuxClick; + Procedure TestEventOnBlur; + Procedure TestEventOnCancel; + Procedure TestEventOnCanPlay; + Procedure TestEventOnCanPlayThrough; + Procedure TestEventOnChange; + Procedure TestEventOnClick; + Procedure TestEventOnCompositionEnd; + Procedure TestEventOnCompositionStart; + Procedure TestEventOnCompositionUpdate; + Procedure TestEventOnContextMenu; + Procedure TestEventOnCopy; + Procedure TestEventOnCut; + Procedure TestEventOnCueChange; + Procedure TestEventOnDblClick; + Procedure TestEventOnDurationChange; + Procedure TestEventOnEnded ; + Procedure TestEventOnError ; + Procedure TestEventOnFocus; + Procedure TestEventOnFocusIn ; + Procedure TestEventOnFocusOut ; + Procedure TestEventOnGotPointerCapture; + Procedure TestEventOnInput; + Procedure TestEventOnInvalid; + Procedure TestEventOnKeyDown; + Procedure TestEventOnKeyPress; + Procedure TestEventOnKeyUp; + Procedure TestEventOnLoad; + Procedure TestEventOnLoadedData; + Procedure TestEventOnLoadedMetaData; + Procedure TestEventOnLoadend; + Procedure TestEventOnLoadStart; + Procedure TestEventOnLostPointerCapture; + Procedure TestEventOnMouseDown; + Procedure TestEventOnMouseEnter; + Procedure TestEventOnMouseLeave; + Procedure TestEventOnMouseMove; + Procedure TestEventOnMouseOut; + Procedure TestEventOnMouseUp; + Procedure TestEventOnOverFlow; + Procedure TestEventOnPaste; + Procedure TestEventOnPause; + Procedure TestEventOnPlay; + Procedure TestEventOnPointerCancel; + Procedure TestEventOnPointerDown; + Procedure TestEventOnPointerEnter; + Procedure TestEventOnPointerLeave; + Procedure TestEventOnPointerMove; + Procedure TestEventOnPointerOut; + Procedure TestEventOnPointerOver; + Procedure TestEventOnPointerUp; + Procedure TestEventOnReset; + Procedure TestEventOnResize; + Procedure TestEventOnScroll; + Procedure TestEventOnSelect; + Procedure TestEventOnSubmit; + Procedure TestEventOnTouchStart; + Procedure TestEventOnTransitionCancel; + Procedure TestEventOnTransitionEnd; + Procedure TestEventOnTransitionRun; + Procedure TestEventOnTransitionStart; + Procedure TestEventOnWheel; + end; + + { TTestWebWidgetStyles } + + TTestWebWidgetStyles = Class(TBaseTestWidget) + private + FMy: TMyChildWidget; + procedure AddDefaults; + procedure AssertStyle(aIdx: integer; const AName, aValue: String; CheckElement: Boolean=True); + function GetItems: TWebWidgetStyles; + procedure GetNonExist; + Public + Procedure Setup; override; + Procedure TearDown; override; + Property MyWidget : TMyChildWidget Read FMy; + Property Styles : TWebWidgetStyles Read GetItems; + Published + Procedure TestEmpty; + Procedure TestAdd; + Procedure TestAddOnRender; + Procedure TestAddAfterRender; + Procedure TestIndexOf; + Procedure TestFind; + Procedure TestGet; + Procedure TestEnsure; + Procedure TestEnsureRendered; + Procedure TestApplyToDOM; + Procedure TestRefreshFromDOM; + Procedure TestDirty; + Procedure TestClearImported; + end; + + { TTestWebWidgetReferences } + + TTestWebWidgetReferences = Class(TBaseTestWidget) + private + FMy: TMyRefWidget; + function GetItems: TWebWidgetReferences; + Public + Procedure Setup; override; + Procedure TearDown; override; + Property MyWidget : TMyRefWidget Read FMy; + Property References : TWebWidgetReferences Read GetItems; + Published + Procedure TestEmpty; + end; + +implementation + +{ TMyRefWidget } + +function TMyRefWidget.DoRenderHTML(aParent, aElement: TJSHTMLElement): TJSHTMLElement; +{ + Final tree should be + <div> <!-- top --> + <div> <!-- element --> + <ul> + <li>item 0</li> + <!-- ... --> + <li>item 9</li> + </ul> + <div> <!-- 'sub' content element --> + </div> + </div> + </div> +} +Var + I : Integer; +begin + Result:=inherited DoRenderHTML(aParent, aElement); + FUL:=TJSHTMLElement(Document.CreateElement('<ul>')); + Result.insertBefore(Ful,FSub); + Result.AppendChild(FUL); + SetLength(FSubs,10); + For I:=0 to 9 do + begin + FSubs[i]:=TJSHTMLElement(Document.CreateElement('li')); + FSubs[i].InnerText:='item '+IntToStr(I); + FUL.AppendChild(FSubs); + end; +end; + +{ TTestWebWidgetReferences } + +function TTestWebWidgetReferences.GetItems: TWebWidgetReferences; +begin + Result:=FMy.References; +end; + +procedure TTestWebWidgetReferences.Setup; +begin + inherited Setup; +end; + +procedure TTestWebWidgetReferences.TearDown; +begin + inherited TearDown; +end; + +procedure TTestWebWidgetReferences.TestEmpty; +begin + +end; + +{ TTestWebWidgetStyles } + +function TTestWebWidgetStyles.GetItems: TWebWidgetStyles; +begin + Result:=MyWidget.Styles; +end; + +procedure TTestWebWidgetStyles.Setup; +begin + inherited Setup; + FMy:=TMyChildWidget.Create(Nil); + FMy.ParentID:=SBaseWindowID; +end; + +procedure TTestWebWidgetStyles.TearDown; +begin + FreeAndNil(FMy); + inherited TearDown; +end; + +procedure TTestWebWidgetStyles.TestEmpty; +begin + AssertNotNull('Have widget',MyWidget); + AssertNotNull('Have widget styles',MyWidget.Styles); + AssertNull('Not yet rendered',MyWidget.MyElement); +end; + +procedure TTestWebWidgetStyles.AssertStyle(aIdx : integer; const AName,aValue : String; CheckElement : Boolean = True); + +begin + AssertTrue('Correct index',(aIdx>=0) and (aIdx<Styles.Count)); + AssertEquals('Correct name at index',aName,Styles[aIdx].name); + AssertEquals('Correct value at index',aValue,Styles[aIdx].Value); + if CheckElement then + begin + AssertNotNull('Must check element, have element',MyWidget.MyElement); + AssertEquals('Correct style applied',aValue,MyWidget.MyElement.style.getPropertyValue(aName)); + end; +end; + + +procedure TTestWebWidgetStyles.TestAdd; +begin + Styles.Add('display','none'); + AssertEquals('Count',1,Styles.Count); + AssertStyle(0,'display','none',False); +end; + +procedure TTestWebWidgetStyles.TestAddOnRender; +begin + TestAdd; + MyWidget.Refresh; + AssertStyle(0,'display','none',True); +end; + +procedure TTestWebWidgetStyles.TestAddAfterRender; + +begin + MyWidget.Refresh; + Styles.Add('display','none'); + AssertStyle(0,'display','none',True); +end; + +procedure TTestWebWidgetStyles.AddDefaults; + +begin + Styles.Add('display','none'); + Styles.Add('width','10px'); + Styles.Add('height','20px'); +end; + +procedure TTestWebWidgetStyles.TestIndexOf; +begin + AddDefaults; + AssertEquals('Non-existing',-1,Styles.IndexOfStyle('wo')); + AssertEquals('display',0,Styles.IndexOfStyle('display')); + AssertEquals('width',1,Styles.IndexOfStyle('width')); + AssertEquals('height',2,Styles.IndexOfStyle('height')); +end; + +procedure TTestWebWidgetStyles.TestFind; +begin + AddDefaults; + AssertSame('Non-existing',nil,Styles.FindStyle('wo')); + AssertSame('display',Styles[0],Styles.FindStyle('display')); + AssertSame('width',Styles[1],Styles.FindStyle('width')); + AssertSame('height',Styles[2],Styles.FindStyle('height')); +end; + +procedure TTestWebWidgetStyles.GetNonExist; + +begin + Styles.GetStyle('Wo'); +end; + +procedure TTestWebWidgetStyles.TestGet; +begin + AddDefaults; + AssertException('Non-existing',EWidgets,@GetNonExist); + AssertSame('display',Styles[0],Styles.FindStyle('display')); + AssertSame('width',Styles[1],Styles.FindStyle('width')); + AssertSame('height',Styles[2],Styles.FindStyle('height')); +end; + +procedure TTestWebWidgetStyles.TestEnsure; + +Var + El : TStyleItem; + +begin + AddDefaults; + El:=Styles.EnsureStyle('background-color','red'); + AssertSame('In coll',El,Styles.FindStyle('background-color')); +end; + +procedure TTestWebWidgetStyles.TestEnsureRendered; + +begin + MyWidget.Refresh; + AddDefaults; + Styles.EnsureStyle('background-color','red'); + AssertStyle(3,'background-color','red',True); +end; + +procedure TTestWebWidgetStyles.TestApplyToDOM; + +Var + El : TJSHTmlElement; + +begin + AddDefaults; + El:=TJSHTmlElement(Document.CreateElement('div')); + Styles.ApplyToDOM(El); + AssertEquals('Set 1','none',EL.Style.getPropertyValue('display')); + AssertEquals('Set 2','10px',EL.Style.getPropertyValue('width')); + AssertEquals('Set 3','20px',EL.Style.getPropertyValue('height')); +end; + +procedure TTestWebWidgetStyles.TestRefreshFromDOM; + +Var + El : TJSHTmlElement; + Idx : integer; + +begin + AddDefaults; + El:=TJSHTmlElement(Document.CreateElement('div')); + El.Style.setProperty('display','block'); + El.Style.setProperty('min-width','40px'); + El.Style.setProperty('min-height','50px'); + Styles.RefreshFromDOM(el,True); + idx:=Styles.IndexOfStyle('display'); + AssertTrue('Have display',idx<>-1); + AssertStyle(idx,'display','block',False); + AssertTrue('display imported',Styles[idx].Imported); + idx:=Styles.IndexOfStyle('min-width'); + AssertTrue('Have min-width',idx<>-1); + AssertStyle(idx,'min-width','40px',False); + AssertTrue('min-width imported',Styles[idx].Imported); + idx:=Styles.IndexOfStyle('min-height'); + AssertTrue('Have min-height',idx<>-1); + AssertStyle(idx,'min-height','50px',False); + AssertTrue('min-height imported',Styles[idx].Imported); +end; + +procedure TTestWebWidgetStyles.TestDirty; +begin + MyWidget.Refresh; + Styles.Add('display','none'); + AssertStyle(0,'display','none',True); + Styles.EnsureStyle('display','block'); + AssertStyle(0,'display','block',True); +end; + +procedure TTestWebWidgetStyles.TestClearImported; +begin + TestRefreshFromDOM; + Styles.ClearImported; + AssertEquals('None left',0,Styles.Count); +end; + +{ TMyPrefixSubContentWidget } + +function TMyPrefixSubContentWidget.DoRenderHTML(aParent, aElement: TJSHTMLElement): TJSHTMLElement; +begin + Result:=inherited DoRenderHTML(aParent, aElement); + FTop:=CreateElement('span',aElement.ID+'-span'); + aParent.removeChild(aElement); + FTop.AppendChild(aElement); + aParent.appendChild(FTop); +end; + +procedure TMyPrefixSubContentWidget.DoUnRender(aParent: TJSHTMLElement); +begin + inherited DoUnRender(aParent); + FTop:=Nil; +end; + +function TMyPrefixSubContentWidget.GetTopElement: TJSHTMLELement; +begin + Result:=FTop; +end; + + +{ TMySubContentWidget } + +function TMySubContentWidget.DoRenderHTML(aParent, aElement: TJSHTMLElement): TJSHTMLElement; +begin + Result:=inherited DoRenderHTML(aParent, aElement); + FSub:=CreateElement('div',aElement.ID+'-sub'); + Result.AppendChild(FSub); +end; + +procedure TMySubContentWidget.DoUnRender(aParent: TJSHTMLElement); +begin + inherited DoUnRender(aParent); + FSub:=nil; +end; + +function TMySubContentWidget.GetContentElement: TJSHTMLELement; +begin + Result:=FSub; +end; + +{ TMyWebWidget } + +function TMyWebWidget.WidgetClasses: String; +begin + Result:=FAdd; +end; + +function TMyWebWidget.DoRenderHTML(aParent,aElement: TJSHTMLElement): TJSHTMLElement; +begin + // Do nothing + if aParent<>Nil then; + Result:=aElement; +end; + +function TMyWebWidget.HTMLTag: String; +begin + Result:='div' +end; + +function TMyWebWidget.MyElement: TJSHTMLElement; +begin + Result:=Element; +end; + +function TMyWebWidget.MyParent: TJSHTMLElement; +begin + Result:=ParentElement; +end; + +function TMyWebWidget.MyContent: TJSHTMLElement; +begin + Result:=ContentElement; +end; + +function TMyWebWidget.MyTop: TJSHTMLElement; + +begin + Result:=TopElement; +end; + +{ TTestWidgetBasicOperations } + +function TTestWidgetBasicOperations.GetTop: TMyPrefixSubContentWidget; +begin + if FMyTop=Nil then + FMyTop:=TMyPrefixSubContentWidget.Create(Nil); + Result:=FMyTop; +end; + +function TTestWidgetBasicOperations.GetMySub: TMySubContentWidget; +begin + If FMySub=Nil then + FMySub:=TMySubContentWidget.Create(Nil); + Result:=FMySub; +end; + +procedure TTestWidgetBasicOperations.SetUp; + +begin + inherited SetUp; + FMy:=TMyChildWidget.Create(Nil); + AssertEquals('Correct tag','div',FMy.HTMLTag); + FMyParent:=TMyParentWidget.Create(Nil); + AssertEquals('Correct parent tag','div',FMyParent.HTMLTag); + FEventCount:=0; + FEventSender:=Nil; + FEventName:=''; + FDefaultPrevented:=False; + FMySub:=Nil; + FMyTop:=Nil; +end; + +procedure TTestWidgetBasicOperations.TearDown; +begin + FreeAndNil(FMySub); + FreeAndNil(FMyTop); + FreeAndNil(FMy); + FreeAndNil(FMyParent); + inherited TearDown; +end; + +procedure TTestWidgetBasicOperations.TriggerEvent(aName: String; aClass : TJSEventClass = Nil); + +Var + ev : TJSEvent; + +begin + if aClass=Nil then + ev:=TJSEvent.New(aName) + else + ev:=aClass.New(aName); + FDefaultPrevented:=FMy.EnsureElement.dispatchEvent(ev); +end; + + + +function TTestWidgetBasicOperations.CreateParentElement: TJSHTMLElement; + + +begin + Result:=TJSHTMLElement(Document.CreateElement('div')); + Result.Id:=SMyParentID; + BaseWindow.AppendChild(Result); +end; + +function TTestWidgetBasicOperations.CreateMyElement(asChild: Boolean = False): TJSHTMLELement; +Var + El : TJSHTMLElement; +begin + if AsChild then + El:=CreateParentElement + else + El:=BaseWindow; + Result:=TJSHTMLElement(Document.CreateElement('div')); + Result.Id:=SMyChildID; + El.AppendChild(Result); +end; + +procedure TTestWidgetBasicOperations.SetParentElement; + +begin + FMy.Parent:=FMyParent; +end; + +procedure TTestWidgetBasicOperations.DoSetElementID; +begin + FMy.ElementID:=SMyChildID; +end; + +procedure TTestWidgetBasicOperations.DoSetParentID; +begin + FMy.ParentID:=SMyParentID;; +end; + +procedure TTestWidgetBasicOperations.DoSetDataset; +begin + MyWidget.Data['name']:='me'; +end; + + +function TTestWidgetBasicOperations.SetupParent: TJSHTMLElement; +begin + Result:=CreateParentElement; + FMyParent.ElementID:=SMyParentID; +end; + +procedure TTestWidgetBasicOperations.TestEmpty; +begin + AssertNotNull(MyWidget); + AssertNotNull(MyParentWidget); +end; + +procedure TTestWidgetBasicOperations.TestNoElementIDAndParentElementID; +begin + DoSetElementID; + AssertException('Cannot set both',EWidgets,@DoSetParentID); +end; + +procedure TTestWidgetBasicOperations.TestNoParentElementIDAndElementID; +begin + DoSetParentID; + AssertException('Cannot set both',EWidgets,@DoSetElementID); +end; + +procedure TTestWidgetBasicOperations.TestParentIDToElement; + +Var + El : TJSHTMLElement; + +begin + El:=CreateParentElement; + DoSetParentID; + AssertSame('Correct parent element',El,MyWidget.MyParent); + AssertSame('Correct content element',MyWidget.MyElement,MyWidget.MyContent); + AssertTree('div('+SMyParentID+')'); +end; + +procedure TTestWidgetBasicOperations.TestElementIDToElement; +Var + El : TJSHTMLElement; + +begin + El:=CreateMyElement; + DoSetElementID; + AssertSame('Correct element',El,MyWidget.MyElement); + AssertSame('Correct content element',El,MyWidget.MyContent); + AssertTree('div('+SMyChildID+')'); + AssertEquals('Have element data',el.ID,String(el.dataset['WwElement'])); + AssertEquals('Have element top data',el.ID,String(el.dataset['WwElementTop'])); + AssertEquals('Have element content data',el.ID,String(el.dataset['WwElementContent'])); +end; + +procedure TTestWidgetBasicOperations.TestSetParent; + +Var + El : TJSHTMLElement; + +begin + El:=SetupParent; + AssertSame('Correct parent element',El,MyParentWidget.MyElement); + MyWidget.Parent:=MyParentWidget; + AssertEquals('Child count correct',1,MyParentWidget.ChildCount); + AssertSame('Correct parent element',El,MyWidget.MyParent); + AssertNull('No element yet',MyWidget.Element); + MyWidget.Refresh; + AssertTrue('Have element ID',MyWidget.ElementID<>''); + AssertTree('div('+SMyParentID+')/div('+MyWidget.ElementID+')'); // No ID assigned ! + MyWidget.Parent:=Nil; + AssertEquals('Child count correct',0,MyParentWidget.ChildCount); + AssertNull('No more parent element ',MyWidget.MyParent); +end; + +procedure TTestWidgetBasicOperations.TestRenderParent; + +Var + El,El2 : TJSHTMLElement; + +begin + El:=SetupParent; + MyWidget.Parent:=MyParentWidget; + MyWidget.Refresh; + El2:=MyWidget.MyElement; + AssertNotNull('Have element',El2); + AssertSame('Have content element',El2,MyWidget.MyContent); + AssertSame('Have correct parent element',El,El2.parentElement); + AssertSame('Have correct parent',El,MyWidget.MyParent); + AssertEquals('Correct ID',el2.ID,MyWidget.ElementiD); + AssertEquals('Have element data',el2.ID,String(el2.dataset['WwElement'])); + AssertEquals('Have element top data',el2.ID,String(el2.dataset['WwElementTop'])); + AssertEquals('Have element content data',el2.ID,String(el2.dataset['WwElementContent'])); +end; + +procedure TTestWidgetBasicOperations.TestRenderParentID; + +Var + El,El2 : TJSHTMLElement; + +begin + El:=CreateParentElement; + MyWidget.ParentID:=el.ID; + MyWidget.Refresh; + El2:=MyWidget.MyElement; + AssertNotNull('Have element',El2); + AssertSame('Have content element',El2,MyWidget.MyContent); + AssertSame('Have correct parent element',El,El2.parentElement); + AssertSame('Have correct parent',El,MyWidget.MyParent); + AssertEquals('Correct ID',el2.ID,MyWidget.ElementiD); + AssertEquals('Have element data',el2.ID,String(el2.dataset['WwElement'])); + AssertEquals('Have element top data',el2.ID,String(el2.dataset['WwElementTop'])); + AssertEquals('Have element content data',el2.ID,String(el2.dataset['WwElementContent'])); +end; + +procedure TTestWidgetBasicOperations.TestUnRenderParent; +Var + El,El2 : TJSHTMLElement; + +begin + El:=SetupParent; + MyWidget.Parent:=MyParentWidget; + MyWidget.Refresh; + El2:=MyWidget.MyElement; + AssertNotNull('Have element',El2); + MyWidget.Parent:=Nil; + AssertEquals('Not rendered any more',0,el.childElementCount); + AssertNull('Have no more element',MyWidget.MyElement); + AssertNull('Have no more parent element',MyWidget.MyParent); +end; + +procedure TTestWidgetBasicOperations.TestUnRenderParentID; +Var + El,El2 : TJSHTMLElement; + +begin + El:=CreateParentElement; + MyWidget.ParentID:=el.ID; + MyWidget.Refresh; + El2:=MyWidget.MyElement; + AssertNotNull('Have element',El2); + MyWidget.ParentID:=''; + AssertEquals('Not rendered any more',0,el.childElementCount); + AssertNull('Have no more element',MyWidget.MyElement); + AssertNull('Have no more parent element',MyWidget.MyParent); +end; + +procedure TTestWidgetBasicOperations.TestUnRenderElementID; + +Var + El : TJSHTMLElement; + +begin + TestElementIDToElement; + El:=MyWidget.MyElement; + // This will unrender + MyWidget.ElementID:=''; + AssertTrue('Have removed element data', IsUndefined(el.dataset['WwElement'])); + AssertTrue('Have removed element top data',isUndefined(el.dataset['WwElementTop'])); + AssertTrue('Have removed element content data',isUndefined(el.dataset['WwElementContent'])); + +end; + +procedure TTestWidgetBasicOperations.TestSubContent; + + +begin + SetupParentElement; + MySubWidget.Parent:=MyParentWidget; + MySubWidget.Refresh; + AssertTree('div('+SMyParentID+')/div('+MySubWidget.ElementID+')/div('+MySubWidget.ElementID+'-sub)'); + AssertNotNull('have content',MySubWidget.MyContent); + AssertSame('content element parent is element',MySubWidget.MyElement,MySubWidget.MyContent.parentElement); +end; + +procedure TTestWidgetBasicOperations.TestTopContent; +begin +{ SetupParentElement; + MySubWidget.Parent:=MyParentWidget; + MySubWidget.Refresh; + AssertTree('div('+SMyParentID+')/span('+MySubWidget.ElementID+'-top)/div('+MySubWidget.ElementID+')/div('+MySubWidget.ElementID+'-sub)'); + AssertNotNull('have content',MySubWidget.MyContent); + AssertNotNull('have top content',MySubWidget.MyTop); + AssertSame('top element parent is element',MyParentWidget.MyContent,MySubWidget.MyTop.parentElement); + AssertSame('element parent is top element',MySubWidget.MyTop,MySubWidget.MyElement.parentElement); + AssertSame('content element parent is element',MySubWidget.MyElement,MySubWidget.MyContent.parentElement);} +end; + +procedure TTestWidgetBasicOperations.TestAddClasses; + +Var + S : String; + +begin + S:=TWebWidget.AddClasses('a b c','d c e'); + AssertEquals('Correctly added, no duplicates','a b c d e',S); +end; + +procedure TTestWidgetBasicOperations.TestAddClassesNormalized; +Var + S : String; + +begin + S:=TWebWidget.AddClasses('a b c','d c e',true); + AssertEquals('Correctly added, no duplicates','a b c d e',S); +end; + +procedure TTestWidgetBasicOperations.TestRemoveClasses; + +Var + S : String; + +begin + S:=TWebWidget.RemoveClasses('a b c','d c e'); + AssertEquals('Correctly removed','a b ',S); +end; + +procedure TTestWidgetBasicOperations.TestRemoveClassesNormalized; +Var + S : String; + +begin + S:=TWebWidget.RemoveClasses('a b c','d c e',True); + AssertEquals('Correctly removed','a b',S); +end; + +procedure TTestWidgetBasicOperations.TestClassesBeforeRender; +begin + SetupParent; + MyWidget.Parent:=MyParentWidget; + MyWidget.Classes:='a b c'; + MyWidget.Refresh; + AssertNotNull('Have element',MyWidget.Element); + AssertEquals('Have element classes ','a b c',MyWidget.Element.className); +end; + +procedure TTestWidgetBasicOperations.TestClassesAfterRender; +begin + SetupParent; + MyWidget.Parent:=MyParentWidget; + MyWidget.Refresh; + AssertNotNull('Have element',MyWidget.Element); + MyWidget.Classes:='a b c'; + AssertEquals('Have element classes ','a b c',MyWidget.Element.className); +end; + +procedure TTestWidgetBasicOperations.TestWidgetClassesBeforeRender; +begin + SetupParent; + MyWidget.Parent:=MyParentWidget; + MyWidget.AddedClasses:='d e c'; + MyWidget.Refresh; + AssertNotNull('Have element',MyWidget.Element); + MyWidget.Classes:='a b c'; + AssertEquals('Have element classes ','a b c d e',MyWidget.Element.className); +end; + +procedure TTestWidgetBasicOperations.TestWidgetClassesAfterRender; +begin + SetupParent; + MyWidget.Parent:=MyParentWidget; + MyWidget.Refresh; + AssertNotNull('Have element',MyWidget.Element); + MyWidget.AddedClasses:='d e c'; + // They are not yet added + AssertEquals('Have element classes before setting','',MyWidget.Element.className); + // but now they are added + MyWidget.Classes:='a b c'; + AssertEquals('Have element classes ','a b c d e',MyWidget.Element.className); +end; + +procedure TTestWidgetBasicOperations.TestClassesOnElementID; + +var + el : TJSHTMLElement; + +begin + el:=CreateMyElement(); + MyWidget.Classes:='a b c'; + MyWidget.ElementID:=SMyChildID; + AssertNotNull('Have element',MyWidget.MyElement); + AssertSame('Have element',El,MyWidget.MyElement); + AssertEquals('Have element classes ','a b c',MyWidget.Element.className); +end; + +procedure TTestWidgetBasicOperations.TestStylesBeforeRender; +begin + MyWidget.ParentID:=BaseID; + MyWidget.Styles.Add('display').Value:='none'; + MyWidget.Refresh; + AssertTree('div('+MyWidget.ElementID+')'); + AssertEquals('have style applied','none',MyWidget.MyElement.style.getPropertyValue('display')); +end; + +procedure TTestWidgetBasicOperations.TestStylesAfterRender; +begin + MyWidget.ParentID:=BaseID; + MyWidget.Refresh; + MyWidget.Styles.Add('display').Value:='none'; + AssertTree('div('+MyWidget.ElementID+')'); + AssertEquals('have style applied','none',MyWidget.MyElement.style.getPropertyValue('display')); +end; + +procedure TTestWidgetBasicOperations.TestStylesRefreshOnRender; + +var + Idx : Integer; + +begin + CreateMyElement(False).Style.setProperty('width','30px'); + AssertTree('div('+MyWidget.ElementID+')'); + MyWidget.ElementID:=SMyChildID; + AssertTree('div('+MyWidget.ElementID+')'); + MyWidget.Refresh; + MyWidget.Styles.Add('display').Value:='none'; + AssertEquals('have style applied','none',MyWidget.MyElement.style.getPropertyValue('display')); + AssertEquals('have pre-existing imported: count',2,MyWidget.Styles.Count); + Idx:=MyWidget.Styles.IndexOfStyle('width'); + AssertTrue('have pre-existing imported: find name',Idx<>-1); + AssertEquals('have pre-existing imported: have value','30px',MyWidget.Styles[idx].Value); + AssertEquals('have pre-existing imported: marked as imported',True,MyWidget.Styles[idx].IMported); +end; + +procedure TTestWidgetBasicOperations.TestVisibleBeforeRender; +begin + MyWidget.Visible:=False; + SetupElement; + AssertFalse('Visible false',MyWidget.Visible); + AssertEquals('Display none','none',MyWidget.MyElement.Style.getPropertyValue('display')); +end; + +procedure TTestWidgetBasicOperations.TestVisibleAfterRender; +begin + SetupElement; + MyWidget.Refresh; + MyWidget.Visible:=False; + AssertFalse('Visible false',MyWidget.Visible); + AssertEquals('Display none','none',MyWidget.MyElement.Style.getPropertyValue('display')); +end; + +procedure TTestWidgetBasicOperations.TestVisibleBeforeRenderPreserves; +begin + MyWidget.Visible:=False; + SetupElement.style.setProperty('display','inline'); + MyWidget.Refresh; + AssertFalse('Visible false',MyWidget.Visible); + AssertEquals('Display none','none',MyWidget.MyElement.Style.getPropertyValue('display')); + MyWidget.Visible:=True; + AssertTrue('Visible true',MyWidget.Visible); + AssertEquals('Display none','inline',MyWidget.MyElement.Style.getPropertyValue('display')); +end; + +procedure TTestWidgetBasicOperations.TestVisibleAfterRenderPreserves; +begin + SetupElement.style.setProperty('display','inline'); + MyWidget.Refresh; + MyWidget.Visible:=False; + AssertFalse('Visible false',MyWidget.Visible); + AssertEquals('Display none','none',MyWidget.MyElement.Style.getPropertyValue('display')); + MyWidget.Visible:=True; + AssertTrue('Visible true',MyWidget.Visible); + AssertEquals('Display none','inline',MyWidget.MyElement.Style.getPropertyValue('display')); +end; + +procedure TTestWidgetBasicOperations.TestGetData; +begin + SetupElement.Dataset['name']:='me'; + AssertEquals('Correctly read','me',MyWidget.Data['name']); +end; + +procedure TTestWidgetBasicOperations.TestSetData; + +var + el : TJSHTMLElement; +begin + El:=SetupElement; + MyWidget.Data['name']:='me'; + AssertEquals('Correctly set','me',String(el.Dataset['name'])); +end; + +procedure TTestWidgetBasicOperations.TestGetDataNotRendered; +begin + AssertEquals('Correctly read','',MyWidget.Data['name']); +end; + +procedure TTestWidgetBasicOperations.TestSetDataNotRendered; +begin + AssertException('Cannot write',EWidgets,@DoSetDataset); +end; + +function TTestWidgetBasicOperations.SetupElement: TJSHTMLElement; + +begin + Result:=CreateMyElement(False); + MyWidget.ElementID:=SMyChildID; +end; + +procedure TTestWidgetBasicOperations.SetupParentElement; +begin + CreateParentElement; + MyParentWidget.ElementID:=SMyParentID; +end; + +procedure TTestWidgetBasicOperations.TestEvent(aName : String); + +begin + TriggerEvent(aName); + AssertEvent(aName,Fmy); +end; + + +procedure TTestWidgetBasicOperations.TestEventClick; +begin + SetupElement; + MyWidget.OnClick:=@MyTestEventHandler; + TestEvent('click'); +end; + +{ --------------------------------------------------------------------- + TBaseTestWidget + ---------------------------------------------------------------------} +procedure TBaseTestWidget.SetUp; +begin + inherited SetUp; + FBaseWindow:=GetElement(BaseID); +end; + +procedure TBaseTestWidget.TearDown; +begin + if Assigned(FBaseWindow) then + FBaseWindow.InnerHTML:=''; + FBaseWindow:=Nil; + inherited TearDown; +end; + +procedure TBaseTestWidget.AssertEvent(aName: String; aElement : TCustomWebWidget = Nil); +begin + AssertEquals('Event handler called',1,FEventCount); + AssertSame('Event handler sender',aElement,FEventSender); + AssertEquals('Event name',aName,FEventName); +end; + +procedure TBaseTestWidget.MyTestEventHandler(Sender: TObject; Event: TJSEvent); +begin + Inc(FEventCount); + FEventSender:=Sender; + FEventName:=Event._type; +end; + +function TBaseTestWidget.FindElement(aID: String): TJSHTMLElement; +begin + Result:=TJSHTMLElement(Document.GetElementById(aID)); +end; + +function TBaseTestWidget.GetElement(aID: String): TJSHTMLElement; +begin + Result:=FindElement(aID); + if Result=Nil then + Fail('No such element : '+aID); +end; + +Function TBaseTestWidget.AssertTree(aParent: TJSHTmlElement; aTree: String) : TJSHTMLElement; + +Var + S,aTag,aID : String; + P : integer; + T : TJSHTMLElement; + +begin + P:=Pos('/',aTree); + if P=0 then + P:=Length(aTree)+1; + aTag:=Copy(aTree,1,P-1); + aTree:=Copy(aTree,P+1,Length(aTree)-P); + P:=Pos('(',aTag); + if P=0 then + P:=Length(aTag)+1; + aID:=Copy(aTag,P+1,Length(aTag)-P); + aTag:=Copy(aTag,1,P-1); + P:=Pos(')',aID); + if P>0 then + aID:=Copy(aID,1,P-1); + // Writeln('Look for <',aTag,'>(',aID,')'); + T:=TJSHTMLElement(aParent.firstElementChild); + While (T<>Nil) and Not (SameText(T.tagName,aTag) and ((aID='') or SameText(T.ID,aID))) do + T:=TJSHTMLElement(T.nextElementSibling); + if (T=Nil) then + begin + S:='Could not find <'+aTag+'>'; + if (aID<>'') then + S:=S+'(ID='+aID+')'; + S:=S+'below element '+aParent.TagName; + if (aParent.ID<>'') then + S:=S+'(ID='+aParent.ID+')'; + Fail(S); + end; + // Writeln('Found: <',T.TagName,'>(iD=',T.ID+')'); + if aTree<>'' then + Result:=AssertTree(T,aTree) + else + Result:=T; +end; + +Function TBaseTestWidget.AssertTree(aTree: String) : TJSHTMLElement; +begin + Result:=AssertTree(BaseWindow,aTree) +end; + +{ --------------------------------------------------------------------- + TTestWidgetBasicOperations + ---------------------------------------------------------------------} + + +procedure TTestWidgetBasicOperations.TestEventOnAbort; + +begin + SetupElement; + MyWidget.OnAbort:=@MyTestEventHandler; + TestEvent('abort'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnAnimationCancel; + +begin + SetupElement; + MyWidget.OnAnimationCancel:=@MyTestEventHandler; + TestEvent('animationcancel'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnAnimationEnd; + +begin + SetupElement; + MyWidget.OnAnimationEnd:=@MyTestEventHandler; + TestEvent('animationend'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnAnimationIteration; + +begin + SetupElement; + MyWidget.OnAnimationIteration:=@MyTestEventHandler; + TestEvent('animationiteration'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnAnimationStart; + +begin + SetupElement; + MyWidget.OnAnimationStart:=@MyTestEventHandler; + TestEvent('animationstart'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnAuxClick; + +begin + SetupElement; + MyWidget.OnAuxClick:=@MyTestEventHandler; + TestEvent('auxclick'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnBlur; + +begin + SetupElement; + MyWidget.OnBlur:=@MyTestEventHandler; + TestEvent('blur'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCancel; + +begin + SetupElement; + MyWidget.OnCancel:=@MyTestEventHandler; + TestEvent('cancel'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCanPlay; + +begin + SetupElement; + MyWidget.OnCanPlay:=@MyTestEventHandler; + TestEvent('canplay'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCanPlayThrough; + +begin + SetupElement; + MyWidget.OnCanPlayThrough:=@MyTestEventHandler; + TestEvent('canplaythrough'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnChange; + +begin + SetupElement; + MyWidget.OnChange:=@MyTestEventHandler; + TestEvent('change'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnClick; + +begin + SetupElement; + MyWidget.OnClick:=@MyTestEventHandler; + TestEvent('click'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCompositionEnd; + +begin + SetupElement; + MyWidget.OnCompositionEnd:=@MyTestEventHandler; + TestEvent('compositionend'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCompositionStart; + +begin + SetupElement; + MyWidget.OnCompositionStart:=@MyTestEventHandler; + TestEvent('compositionstart'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCompositionUpdate; + +begin + SetupElement; + MyWidget.OnCompositionUpdate:=@MyTestEventHandler; + TestEvent('compositionupdate'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnContextMenu; + +begin + SetupElement; + MyWidget.OnContextMenu:=@MyTestEventHandler; + TestEvent('contextmenu'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCopy; + +begin + SetupElement; + MyWidget.OnCopy:=@MyTestEventHandler; + TestEvent('copy'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCut; + +begin + SetupElement; + MyWidget.OnCut:=@MyTestEventHandler; + TestEvent('cut'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnCueChange; + +begin + SetupElement; + MyWidget.OnCueChange:=@MyTestEventHandler; + TestEvent('cuechange'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnDblClick; + +begin + SetupElement; + MyWidget.OnDblClick:=@MyTestEventHandler; + TestEvent('dblclick'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnDurationChange; + +begin + SetupElement; + MyWidget.OnDurationChange:=@MyTestEventHandler; + TestEvent('durationchange'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnEnded; + +begin + SetupElement; + MyWidget.OnEnded :=@MyTestEventHandler; + TestEvent('ended'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnError; + +begin + SetupElement; + MyWidget.OnError :=@MyTestEventHandler; + TestEvent('error'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnFocus; + +begin + SetupElement; + MyWidget.OnFocus:=@MyTestEventHandler; + TestEvent('focus'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnFocusIn; + +begin + SetupElement; + MyWidget.OnFocusIn :=@MyTestEventHandler; + TestEvent('focusin'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnFocusOut; + +begin + SetupElement; + MyWidget.OnFocusOut :=@MyTestEventHandler; + TestEvent('focusout'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnGotPointerCapture; + +begin + SetupElement; + MyWidget.OnGotPointerCapture:=@MyTestEventHandler; + TestEvent('gotpointercapture'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnInput; + +begin + SetupElement; + MyWidget.OnInput:=@MyTestEventHandler; + TestEvent('input'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnInvalid; + +begin + SetupElement; + MyWidget.OnInvalid:=@MyTestEventHandler; + TestEvent('invalid'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnKeyDown; + +begin + SetupElement; + MyWidget.OnKeyDown:=@MyTestEventHandler; + TestEvent('keydown'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnKeyPress; + +begin + SetupElement; + MyWidget.OnKeyPress:=@MyTestEventHandler; + TestEvent('keypress'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnKeyUp; + +begin + SetupElement; + MyWidget.OnKeyUp:=@MyTestEventHandler; + TestEvent('keyup'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnLoad; + +begin + SetupElement; + MyWidget.OnLoad:=@MyTestEventHandler; + TestEvent('load'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnLoadedData; + +begin + SetupElement; + MyWidget.OnLoadedData:=@MyTestEventHandler; + TestEvent('loadeddata'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnLoadedMetaData; + +begin + SetupElement; + MyWidget.OnLoadedMetaData:=@MyTestEventHandler; + TestEvent('loadedmetadata'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnLoadend; + +begin + SetupElement; + MyWidget.OnLoadend:=@MyTestEventHandler; + TestEvent('loadend'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnLoadStart; + +begin + SetupElement; + MyWidget.OnLoadStart:=@MyTestEventHandler; + TestEvent('loadstart'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnLostPointerCapture; + +begin + SetupElement; + MyWidget.OnLostPointerCapture:=@MyTestEventHandler; + TestEvent('lostpointercapture'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnMouseDown; + +begin + SetupElement; + MyWidget.OnMouseDown:=@MyTestEventHandler; + TestEvent('mousedown'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnMouseEnter; + +begin + SetupElement; + MyWidget.OnMouseEnter:=@MyTestEventHandler; + TestEvent('mouseenter'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnMouseLeave; + +begin + SetupElement; + MyWidget.OnMouseLeave:=@MyTestEventHandler; + TestEvent('mouseleave'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnMouseMove; + +begin + SetupElement; + MyWidget.OnMouseMove:=@MyTestEventHandler; + TestEvent('mousemove'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnMouseOut; + +begin + SetupElement; + MyWidget.OnMouseOut:=@MyTestEventHandler; + TestEvent('mouseout'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnMouseUp; + +begin + SetupElement; + MyWidget.OnMouseUp:=@MyTestEventHandler; + TestEvent('mouseup'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnOverFlow; + +begin + SetupElement; + MyWidget.OnOverFlow:=@MyTestEventHandler; + TestEvent('overflow'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPaste; + +begin + SetupElement; + MyWidget.OnPaste:=@MyTestEventHandler; + TestEvent('paste'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPause; + +begin + SetupElement; + MyWidget.OnPause:=@MyTestEventHandler; + TestEvent('pause'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPlay; + +begin + SetupElement; + MyWidget.OnPlay:=@MyTestEventHandler; + TestEvent('play'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPointerCancel; + +begin + SetupElement; + MyWidget.OnPointerCancel:=@MyTestEventHandler; + TestEvent('pointercancel'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPointerDown; + +begin + SetupElement; + MyWidget.OnPointerDown:=@MyTestEventHandler; + TestEvent('pointerdown'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPointerEnter; + +begin + SetupElement; + MyWidget.OnPointerEnter:=@MyTestEventHandler; + TestEvent('pointerenter'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPointerLeave; + +begin + SetupElement; + MyWidget.OnPointerLeave:=@MyTestEventHandler; + TestEvent('pointerleave'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPointerMove; + +begin + SetupElement; + MyWidget.OnPointerMove:=@MyTestEventHandler; + TestEvent('pointermove'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPointerOut; + +begin + SetupElement; + MyWidget.OnPointerOut:=@MyTestEventHandler; + TestEvent('pointerout'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPointerOver; + +begin + SetupElement; + MyWidget.OnPointerOver:=@MyTestEventHandler; + TestEvent('pointerover'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnPointerUp; + +begin + SetupElement; + MyWidget.OnPointerUp:=@MyTestEventHandler; + TestEvent('pointerup'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnReset; + +begin + SetupElement; + MyWidget.OnReset:=@MyTestEventHandler; + TestEvent('reset'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnResize; + +begin + SetupElement; + MyWidget.OnResize:=@MyTestEventHandler; + TestEvent('resize'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnScroll; + +begin + SetupElement; + MyWidget.OnScroll:=@MyTestEventHandler; + TestEvent('scroll'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnSelect; + +begin + SetupElement; + MyWidget.OnSelect:=@MyTestEventHandler; + TestEvent('select'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnSubmit; + +begin + SetupElement; + MyWidget.OnSubmit:=@MyTestEventHandler; + TestEvent('submit'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnTouchStart; + +begin + SetupElement; + MyWidget.OnTouchStart:=@MyTestEventHandler; + TestEvent('touchstart'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnTransitionCancel; + +begin + SetupElement; + MyWidget.OnTransitionCancel:=@MyTestEventHandler; + TestEvent('transitioncancel'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnTransitionEnd; + +begin + SetupElement; + MyWidget.OnTransitionEnd:=@MyTestEventHandler; + TestEvent('transitionend'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnTransitionRun; + +begin + SetupElement; + MyWidget.OnTransitionRun:=@MyTestEventHandler; + TestEvent('transitionrun'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnTransitionStart; + +begin + SetupElement; + MyWidget.OnTransitionStart:=@MyTestEventHandler; + TestEvent('transitionstart'); +end; + +procedure TTestWidgetBasicOperations.TestEventOnWheel; + +begin + SetupElement; + MyWidget.OnWheel:=@MyTestEventHandler; + TestEvent('wheel'); +end; + +initialization + RegisterTests([TTestWidgetBasicOperations,TTestWebWidgetStyles]); +end. + diff --git a/packages/webwidget/tests/testwidgets.html b/packages/webwidget/tests/testwidgets.html new file mode 100644 index 0000000..d3ec484 --- /dev/null +++ b/packages/webwidget/tests/testwidgets.html @@ -0,0 +1,19 @@ +<!doctype html> +<html lang="en"> +<head> + <meta http-equiv="Content-type" content="text/html; charset=utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Test widgets + + + + +
+
+
+
+ + + diff --git a/packages/webwidget/tests/testwidgets.lpi b/packages/webwidget/tests/testwidgets.lpi new file mode 100644 index 0000000..a350984 --- /dev/null +++ b/packages/webwidget/tests/testwidgets.lpi @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + <UseAppBundle Value="False"/> + <ResourceType Value="res"/> + </General> + <CustomData Count="2"> + <Item0 Name="MaintainHTML" Value="1"/> + <Item1 Name="PasJSWebBrowserProject" Value="1"/> + </CustomData> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + <Modes Count="0"/> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="lazwebwidgets"/> + </Item1> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="testwidgets.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="testwidgets.html"/> + <IsPartOfProject Value="True"/> + <CustomData Count="1"> + <Item0 Name="PasJSIsProjectHTMLFile" Value="1"/> + </CustomData> + </Unit> + <Unit> + <Filename Value="btnrun.pp"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="tcwidget.pp"/> + <IsPartOfProject Value="True"/> + <UnitName Value="tcWidget"/> + </Unit> + <Unit> + <Filename Value="tchtmlwidgets.pp"/> + <IsPartOfProject Value="True"/> + <UnitName Value="tcHTMLWidgets"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target FileExt=".js"> + <Filename Value="testwidgets"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="js"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <AllowLabel Value="False"/> + <CPPInline Value="False"/> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> + <CodeGeneration> + <TargetOS Value="browser"/> + </CodeGeneration> + <Linking> + <Debugging> + <GenerateDebugInfo Value="False"/> + <UseLineInfoUnit Value="False"/> + </Debugging> + </Linking> + <Other> + <CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/> + <CompilerPath Value="$(pas2js)"/> + </Other> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/packages/webwidget/tests/testwidgets.lpr b/packages/webwidget/tests/testwidgets.lpr new file mode 100644 index 0000000..0ee110c --- /dev/null +++ b/packages/webwidget/tests/testwidgets.lpr @@ -0,0 +1,16 @@ +program testwidgets; + +{$mode objfpc} + +uses + browserconsole, {browsertestrunner} consoletestrunner, JS, Classes, SysUtils, Web, btnrun, tcWidget, tchtmlwidgets; + +var + Application : TTestRunner; + +begin + Application:=TTestRunner.Create(nil); + Application.RunFormClass:=TConsoleRunner; + Application.Initialize; + Application.Run; +end. diff --git a/packages/webwidget/webwidget.pas b/packages/webwidget/webwidget.pas new file mode 100644 index 0000000..314110f --- /dev/null +++ b/packages/webwidget/webwidget.pas @@ -0,0 +1,1977 @@ +unit webwidget; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, JS, Web; + +Const + + SElementData = 'wwElement'; + STopElementData = SElementData+'Top'; + SContentElementData = SElementData+'Content'; + SElementClass = 'wwClass'; + + sEventAbort = 'abort'; + SEventAnimationCancel = 'animationcancel'; + SEventAnimationEnd = 'animationend'; + SEventAnimationIteration = 'animationiteration'; + SEventAnimationStart = 'animationstart'; + sEventAuxClick = 'auxclick'; + sEventBlur = 'blur'; + SEventCancel = 'cancel'; + SEventCanPlay = 'canplay'; + SEventCanPlayThrough = 'canplaythrough'; + SEventChange = 'change'; + sEventClick = 'click'; + sEventCompositionEnd = 'compositionend'; + sEventCompositionStart = 'compositionstart'; + sEventCompositionUpdate = 'compositionupdate'; + sEventContextMenu = 'contextmenu'; + sEventCopy = 'copy'; + sEventCut = 'cut'; + sEventCueChange = 'cuechange'; + sEventDblClick = 'dblclick'; + sEventDurationChange = 'durationchange'; + sEventEnded = 'ended'; + sEventError = 'error'; + sEventFocus = 'focus'; + sEventFocusIn = 'focusin'; + sEventFocusOut = 'focusout'; + SEventGotPointerCapture = 'gotpointercapture'; + SEventInput = 'input'; + SEventInvalid = 'invalid'; + sEventKeyDown = 'keydown'; + sEventKeyPress = 'keypress'; + sEventKeyUp = 'keyup'; + sEventLoad = 'load'; + sEventLoadedData = 'loadeddata'; + sEventLoadedMetaData = 'loadedmetadata'; + sEventLoadend = 'loadend'; + sEventLoadStart = 'loadstart'; + SEventLostPointerCapture = 'lostpointercapture'; + sEventMouseDown = 'mousedown'; + sEventMouseEnter = 'mouseenter'; + sEventMouseLeave = 'mouseleave'; + sEventMouseMove = 'mousemove'; + sEventMouseOut = 'mouseout'; + sEventMouseUp = 'mouseup'; + sEventOverFlow = 'overflow'; + sEventPaste = 'paste'; + sEventPause = 'pause'; + sEventPlay = 'play'; + SEventPointerCancel = 'pointercancel'; + SEventPointerDown = 'pointerdown'; + SEventPointerEnter = 'pointerenter'; + SEventPointerLeave = 'pointerleave'; + SEventPointerMove = 'pointermove'; + SEventPointerOut = 'pointerout'; + SEventPointerOver = 'pointerover'; + SEventPointerUp = 'pointerup'; + sEventReset = 'reset'; + sEventResize = 'resize'; + sEventScroll = 'scroll'; + sEventSelect = 'select'; + sEventSubmit = 'submit'; + sEventTouchStart = 'touchstart'; + SEventTransitionCancel = 'transitioncancel'; + SEventTransitionEnd = 'transitionend'; + SEventTransitionRun = 'transitionrun'; + SEventTransitionStart = 'transitionstart'; + SEventWheel = 'wheel'; + SErrNotRendered = 'Cannot perform this operation: Widget not rendered'; + + +Type + EWidgets = Class(Exception); + TCustomWebWidget = Class; + + THTMLNotifyEvent = Procedure (Sender : TObject; Event : TJSEvent) of object; + + TEventDispatch = Record + MsgStr : String; + HTMLEvent : TJSEvent; + EventHandler : THTMLNotifyEvent; + end; + + { TStyleItem } + TStylePriority = (spNone,spImportant); + + TStyleItem = Class(TCollectionItem) + private + FPriority: TStylePriority; + FName: String; + FValue: String; + FImported : Boolean; + procedure SetPriority(AValue: TStylePriority); + procedure SetValue(AValue: String); + procedure SetName(AValue: String); + Protected + procedure MarkDirty; + Public + Property Imported : Boolean read FImported; + Procedure Assign(Source : TPersistent) ; override; + Published + Property Name : String Read FName Write SetName; + Property Value : String Read FValue Write SetValue; + Property Priority : TStylePriority Read FPriority Write SetPriority; + end; + + { TWebWidgetStyles } + + TWebWidgetStyles = Class(TOwnedCollection) + private + Function GetStyleItem(aIndex : Integer): TStyleItem; + procedure SetItem(aIndex : Integer; AValue: TStyleItem); + Protected + Procedure MarkDirty(aItem : TStyleItem); + Procedure ApplyToDOM(aElement : TJSHTMlElement;aItem : TStyleItem); virtual; overload; + Public + Function Widget : TCustomWebWidget; + // Manipulate + Function Add(Const aName : String; const aValue : String= '') : TStyleItem; overload; + Function EnsureStyle(Const aName : String; const aValue : String= '') : TStyleItem; + Function IndexOfStyle(Const aName : String) : integer; + Function FindStyle(Const aName : String) : TStyleItem; + Function GetStyle(Const aName : String) : TStyleItem; + Function RemoveStyle(Const aName : String) : String; + Procedure RefreshFromDOM(aElement : TJSHTMlElement = Nil;DoClear : Boolean = True);virtual; + Procedure ClearImported; + Procedure ApplyToDOM(aElement : TJSHTMlElement = Nil); virtual; overload; + Property Styles[aIndex : Integer] : TStyleItem Read GetStyleItem Write SetItem; default; + end; + + + TStyleRefresh = (srOnElementID, // Only refresh styles if ElementID was set and we bind to existing element. + srAlways, // Always refresh styles + srNever); // Never refresh + TStyleRefreshes = Set of TStyleRefresh; + + { TReferenceItem } + TJSHTMLElementArray = Array of TJSHTMLElement; + + TReferenceItem = Class(TCollectionItem) + private + FName: String; + FSelector: String; + procedure SetName(AValue: String); + procedure SetSelector(AValue: String); + function GetElement: TJSHTMLElement; + function GetElements: TJSHTMLElementArray; + Protected + Procedure MarkDirty; virtual; + Public + Procedure Refresh; + Property Element : TJSHTMLElement Read GetElement; + Property Elements : TJSHTMLElementArray Read GetElements; + Published + Property Selector : String Read FSelector Write SetSelector; + Property Name : String Read FName Write SetName; + end; + + { TWebWidgetReferences } + + TWebWidgetReferences = Class(TOwnedCollection) + Private + FRefs : TJSObject; // Arrays of elements, even for single element + function GetReferenceItem(aIndex : Integer): TReferenceItem; + procedure SetReferenceItem(aIndex : Integer; AValue: TReferenceItem); + Protected + Procedure MarkDirty(aItem : TReferenceItem); + Procedure RefreshFromDOM(aItem : TReferenceItem;aElement : TJSHTMlElement); + Property Refs : TJSObject Read FRefs; + Public + Function Widget : TCustomWebWidget; + // Manipulate + Function Add(Const aName : String; aSelector : String = '') : TReferenceItem; overload; + Function EnsureReference(Const aName : String) : TReferenceItem; + Function IndexOfReference(Const aName : String) : TReferenceItem; + Function FindReference(Const aName : String) : TReferenceItem; + Function GetReference(Const aName : String) : TReferenceItem; + Procedure RemoveReference(Const aName : String); + Function GetElementByName(Const aName : String) : TJSHTMLElement; + Function GetElementsByName(Const aName : String) : TJSHTMLElementArray; + Procedure RefreshFromDOM(aElement : TJSHTMlElement = Nil);virtual; + Property References[aIndex : Integer] : TReferenceItem Read GetReferenceItem Write SetReferenceItem; default; + end; + +{$DispatchStrField name} + { TCustomWebWidget } + + TCustomWebWidget = Class(TComponent) + Private + const MaxEvents = 66; + Class Var WidgetID : NativeInt; + Const FEventNames : Array[0..MaxEvents] of String = ( + // When adding, only add at the end !! + sEventAbort, //0 + SEventAnimationCancel, + SEventAnimationEnd, + SEventAnimationIteration, + SEventAnimationStart, + sEventAuxClick , + sEventBlur , + SEventCancel , + SEventCanPlay , + SEventCanPlayThrough , + SEventChange , // 10 + sEventClick , + sEventCompositionEnd , + sEventCompositionStart , + sEventCompositionUpdate , + sEventContextMenu , + sEventCopy , + sEventCut , + sEventCueChange , + sEventDblClick , + sEventDurationChange , // 20 + sEventEnded , + sEventError , + sEventFocus , + sEventFocusIn , + sEventFocusOut , + SEventGotPointerCapture , + SEventInput , + SEventInvalid , + sEventKeyDown , + sEventKeyPress , // 30 + sEventKeyUp , + sEventLoad , + sEventLoadedData , + sEventLoadedMetaData , + sEventLoadend , + sEventLoadStart , + SEventLostPointerCapture , + sEventMouseDown , + sEventMouseEnter , + sEventMouseLeave , // 40 + sEventMouseMove , + sEventMouseOut , + sEventMouseUp , + sEventOverFlow , + sEventPaste , + sEventPause , + sEventPlay , + SEventPointerCancel , + SEventPointerDown , + SEventPointerEnter , + SEventPointerLeave , + SEventPointerMove , + SEventPointerOut , + SEventPointerOver , + SEventPointerUp , + sEventReset , + sEventResize , + sEventScroll , + sEventSelect , + sEventSubmit , + sEventTouchStart , + SEventTransitionCancel , + SEventTransitionEnd , + SEventTransitionRun , + SEventTransitionStart , + SEventWheel + ); + private + FAfterRenderHTML: TNotifyEvent; + FAfterUnRenderHTML: TNotifyEvent; + FBeforeRenderHTML: TNotifyEvent; + FBeforeUnRenderHTML: TNotifyEvent; + FParent : TCustomWebWidget; + FMyHook : TJSRawEventHandler; + // Set by setting ParentID or Parent + FParentElement : TJSHTMLElement; + FElement : TJSHTMLElement; + FOwnsElement : Boolean; + FParentID : String; + FElementID: String; + FChildren : TJSArray; + FClasses : String; + FMyEvents : TJSObject; + FStyleRefresh: TStyleRefresh; + FStyles: TWebWidgetStyles; + FVisible : Boolean; + FDisplay : String; + FReferences : TWebWidgetReferences; + function GetChildCount: Integer; + function GetChild(aIndex : Integer): TCustomWebWidget; + function GetClasses: String; + function GetDataset(aName : String): String; + function GetElement: TJSHTMLELement; + function GetExternalElement: Boolean; + function GetHaveReferences: Boolean; + function GetHTMLEvent(AIndex: Integer): THTMLNotifyEvent; + function GetIsElementDirty: Boolean; + function GetParent: TCustomWebWidget; + function GetParentElement: TJSHTMLELement; + function GetParentID: String; + function GetElementID: String; + function GetReferences: TWebWidgetReferences; + function GetRendered: Boolean; + function GetVisible: Boolean; + procedure SetClasses(AValue: String); + procedure SetDataset(aName : String; AValue: String); + procedure SetElementID(AValue: String); + procedure SetHTMLEvent(AIndex: Integer; AValue: THTMLNotifyEvent); + procedure SetParent(AValue: TCustomWebWidget); + procedure SetParentID(AValue: String); + Procedure AddChild(aValue : TCustomWebWidget); + Procedure RemoveChild(aValue : TCustomWebWidget); + procedure SetReferences(AValue: TWebWidgetReferences); + procedure SetStyles(AValue: TWebWidgetStyles); + procedure SetVisible(AValue: Boolean); + // This protected section is not meant to be made public + Protected + // Events mechanism + procedure EventEntry(aEvent: TJSEvent); virtual; + // Low level + procedure RemoveEvent(aElement: TJSHTMLElement; const aEvent: String); + procedure HookupEvent(aElement: TJSHTMLElement; const aEvent: String); + Procedure HookupEvents(aElement : TJSHTMLElement); virtual; + // Add to internal list, if rendered, calls hookup + Procedure AddEvent(aName : String; AHandler : THTMLNotifyEvent); + // Remove from internal list, if rendered, calls RemoveEvent + Procedure DeleteEvent(aName : String); + // Override these if you want somehow to grab a fixed element on a page. + Class Function FixedParent : TJSHTMLElement; virtual; + Class Function DefaultParentElement : TJSHTMLElement; virtual; + Class Function DefaultParent : TCustomWebWidget; virtual; + Class Function FixedElement : TJSHTMLElement; virtual; + // Generate an ID + Class function GenerateID: String; + // Find element in DOM tree. + Class Function FindElement(aID : String) : TJSHTMLElement; + // Create element in DOM tree, set ID if it is nonzero + Class function CreateElement (aTag : String; aID : String) : TJSHTMLElement; + // override if you want content to go in this sub-element instead of directly below the toplevel element. + function GetContentElement: TJSHTMLELement; virtual; + // Override this if Element is not the top element of this widget. + function GetTopElement: TJSHTMLELement; virtual; + // Auxiliary function to create a displayable name of this widget + Function DisplayElementName : String; + // Make sure there is an element. + function EnsureElement: TJSHTMLElement; + // Set parent element to nil. + Procedure InvalidateParentElement; + // Set element to nil, clears styles + Procedure InvalidateElement; + // Name of the tag to create. Set to '' if you don't want RenderHTML to create one. + Function HTMLTag : String; virtual; abstract; + // Class names that must always be applied for this widget to work. + // They are only added during render, and do not show up in classes property. + // e.g. for a bootstrap button widget, this would return "btn" + Function WidgetClasses : String; virtual; + // Override this if you want to create custom styles collection + Function CreateStyles : TWebWidgetStyles; virtual; + // Override this if you want to create custom references + Function CreateReferences : TWebWidgetReferences; virtual; + // Forces apply of visible, sets Visible property + procedure ApplyVisible(aElement : TJSHTMLElement; AValue: Boolean); virtual; + // Here all properties from the widget are applied to the element. + // This is called during RenderHTML, but also when binding ElementID to an Element. + Procedure ApplyWidgetSettings(aElement : TJSHTMLElement); virtual; + { + Actually render element. + This gets the element as created by RenderHTML and the parent as received by RenderHTML. + If aElement is nil, the DoRenderHTML is responsible for attaching it to the parent element. + Must return the value for Element. + } + Function DoRenderHTML(aParent,aElement : TJSHTMLElement) :TJSHTMLElement; virtual; + // Apply data to Element, Top and Content. Can only be called when the 3 are set, i.e. after RenderHTML or when Element is set from ElementID. + Procedure ApplyData; virtual; + Procedure RemoveData; virtual; + // Create html. Creates element below parent, and renders HTML using doRenderHTML + Function RenderHTML(aParent : TJSHTMLELement) : TJSHTMLElement; + Procedure DoUnRender(aParent : TJSHTMLElement) ; virtual; + // Remove HTML, if any. aParent can be nil. + Procedure UnRender(aParent : TJSHTMLElement); + // Dispatch an event + Function DispatchEvent(aName : String; aEvent : TJSEvent = Nil) : Boolean; + // the rendered or attached element if ElementID was set. Can be Nil; + // This is the "main" widget of the rendered HTML. There can be HTML below or HTML before. + Property Element : TJSHTMLELement Read GetElement; + // The attached parent element. Obtained through Parent or ParentID. Can be Nil; + // Not necessarily the parent element of Element, but definitely the parent of TopElement; + Property ParentElement : TJSHTMLELement Read GetParentElement; + // Content Element. By default equals Element. + Property ContentElement : TJSHTMLELement Read GetContentElement; + // Top Element. The parent element is the direct parent of this element. + Property TopElement : TJSHTMLELement Read GetTopElement; + // Is true if this class created the element (i.e. it was not obtained with ElementID) + Property OwnsElement : Boolean Read FOwnsElement; + // My Events + Property MyEvents : TJSObject Read FMyEvents; + // Return true if the ElementID is referring to an existing element. + Property ExternalElement : Boolean Read GetExternalElement; + // since reading references creates the collection, we want a way to see if there are any without creating them. + Property HaveReferences : Boolean Read GetHaveReferences; + Public + Constructor Create(aOwner : TComponent); override; + Destructor Destroy; override; + // Does this element allow childern ? + Class Function AllowChildren : Boolean; virtual; + // Manipulate Classes + Class Function RemoveClasses(const Source, aClasses : String; Normalize : Boolean = false) : String; + Class Function RemoveClasses(el : TJSHTMLElement; const aClasses : String; Normalize : Boolean = false) : String; + Class Function AddClasses(const Source, aClasses : String; Normalize : Boolean = false) : String; + Class Function AddClasses(el : TJSHTMLElement; const aClasses : String; Normalize : Boolean = false) : String; + // Manipulate styles + function EnsureStyle(const aName: String): TStyleItem; + function AddStyle(const aName,aValue: String): TStyleItem; + function GetStyleValue(const aName : String) : String; + function RemoveStyle(const aName: String): String; + // Remove data from dataset + Procedure RemoveData(const aName : String); + // Re-render + Procedure Refresh; + // These work on the classes property, and on the current element if rendered. Returns the new value of classes. + Function RemoveClasses(const aClasses : String; Normalize : Boolean = false) : String; + Function AddClasses(const aClasses : String; Normalize : Boolean = false) : String; + // Finding widgets + Function FindWidgetByID(aElementID : String; Recurse : Boolean = True) : TCustomWebWidget; + Function GetWidgetByID(aElementID : String; Recurse : Boolean = True) : TCustomWebWidget; + + // For complex HTML, this is the toplevel element + Property Parent : TCustomWebWidget Read GetParent Write SetParent; + // Are we rendered, i.e. is Element valid ? + Property IsRendered : Boolean Read GetRendered; + // Do we need to refresh our internal properties from the element? Currently true if rendered. + // Use this when reading properties and you want/need to refresh a property from the element. + Property IsElementDirty : Boolean Read GetIsElementDirty; + // Child widgets. Note that this can differ significantly from + Property ChildCount : Integer Read GetChildCount; + Property Children [aIndex : Integer] : TCustomWebWidget Read GetChild; + Property Data[aName : String] : String Read GetDataset Write SetDataset; + // This works with style Display: none. + Property Visible : Boolean Read GetVisible Write SetVisible; + // This protected section can be published in descendents + Protected + // Parent or Element ID: Will be used when determining the HTML element when rendering. + // Only one of the Parent or Element ID can be set. + Property ParentID : String Read GetParentID Write SetParentID; + Property ElementID : String Read GetElementID Write SetElementID; + // When reading, returns the actual classes if rendered. + // When rendering, these classes are added to any existing ones if the element exists. + Property Classes : String Read GetClasses Write SetClasses; + // Apply these styles when rendering. Depending on StyleRefresh, styles are imported from actual element. + Property Styles: TWebWidgetStyles Read FStyles Write SetStyles; + // When rendering, should we refresh styles ? + Property StyleRefresh : TStyleRefresh Read FStyleRefresh Write FStyleRefresh; + // Possible references to sub widgets, based on CSS selectors + Property References : TWebWidgetReferences Read GetReferences write SetReferences; + // Events of TWebWidget + Property BeforeRenderHTML : TNotifyEvent Read FBeforeRenderHTML Write FBeforeRenderHTML; + Property AfterRenderHTML : TNotifyEvent Read FAfterRenderHTML Write FAfterRenderHTML; + Property BeforeUnRenderHTML : TNotifyEvent Read FBeforeUnRenderHTML Write FBeforeUnRenderHTML; + Property AfterUnRenderHTML : TNotifyEvent Read FAfterUnRenderHTML Write FAfterUnRenderHTML; + // HTML DOM events + Property OnAbort: THTMLNotifyEvent Index 0 Read GetHTMLEvent Write SetHTMLEvent; + Property OnAnimationCancel: THTMLNotifyEvent Index 1 Read GetHTMLEvent Write SetHTMLEvent; + Property OnAnimationEnd: THTMLNotifyEvent Index 2 Read GetHTMLEvent Write SetHTMLEvent; + Property OnAnimationIteration: THTMLNotifyEvent Index 3 Read GetHTMLEvent Write SetHTMLEvent; + Property OnAnimationStart: THTMLNotifyEvent Index 4 Read GetHTMLEvent Write SetHTMLEvent; + Property OnAuxClick : THTMLNotifyEvent Index 5 Read GetHTMLEvent Write SetHTMLEvent; + Property OnBlur : THTMLNotifyEvent Index 6 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCancel : THTMLNotifyEvent Index 7 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCanPlay : THTMLNotifyEvent Index 8 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCanPlayThrough : THTMLNotifyEvent Index 9 Read GetHTMLEvent Write SetHTMLEvent; + Property OnChange : THTMLNotifyEvent Index 10 Read GetHTMLEvent Write SetHTMLEvent; + Property OnClick : THTMLNotifyEvent Index 11 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCompositionEnd : THTMLNotifyEvent Index 12 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCompositionStart : THTMLNotifyEvent Index 13 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCompositionUpdate : THTMLNotifyEvent Index 14 Read GetHTMLEvent Write SetHTMLEvent; + Property OnContextMenu : THTMLNotifyEvent Index 15 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCopy : THTMLNotifyEvent Index 16 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCut : THTMLNotifyEvent Index 17 Read GetHTMLEvent Write SetHTMLEvent; + Property OnCueChange : THTMLNotifyEvent Index 18 Read GetHTMLEvent Write SetHTMLEvent; + Property OnDblClick : THTMLNotifyEvent Index 19 Read GetHTMLEvent Write SetHTMLEvent; + Property OnDurationChange : THTMLNotifyEvent Index 20 Read GetHTMLEvent Write SetHTMLEvent; + Property OnEnded : THTMLNotifyEvent Index 21 Read GetHTMLEvent Write SetHTMLEvent; + Property OnError : THTMLNotifyEvent Index 22 Read GetHTMLEvent Write SetHTMLEvent; + Property OnFocus : THTMLNotifyEvent Index 23 Read GetHTMLEvent Write SetHTMLEvent; + Property OnFocusIn : THTMLNotifyEvent Index 24 Read GetHTMLEvent Write SetHTMLEvent; + Property OnFocusOut : THTMLNotifyEvent Index 25 Read GetHTMLEvent Write SetHTMLEvent; + Property OnGotPointerCapture : THTMLNotifyEvent Index 26 Read GetHTMLEvent Write SetHTMLEvent; + Property OnInput : THTMLNotifyEvent Index 27 Read GetHTMLEvent Write SetHTMLEvent; + Property OnInvalid : THTMLNotifyEvent Index 28 Read GetHTMLEvent Write SetHTMLEvent; + Property OnKeyDown : THTMLNotifyEvent Index 29 Read GetHTMLEvent Write SetHTMLEvent; + Property OnKeyPress : THTMLNotifyEvent Index 30 Read GetHTMLEvent Write SetHTMLEvent; + Property OnKeyUp : THTMLNotifyEvent Index 31 Read GetHTMLEvent Write SetHTMLEvent; + Property OnLoad : THTMLNotifyEvent Index 32 Read GetHTMLEvent Write SetHTMLEvent; + Property OnLoadedData : THTMLNotifyEvent Index 33 Read GetHTMLEvent Write SetHTMLEvent; + Property OnLoadedMetaData : THTMLNotifyEvent Index 34 Read GetHTMLEvent Write SetHTMLEvent; + Property OnLoadend : THTMLNotifyEvent Index 35 Read GetHTMLEvent Write SetHTMLEvent; + Property OnLoadStart : THTMLNotifyEvent Index 36 Read GetHTMLEvent Write SetHTMLEvent; + Property OnLostPointerCapture : THTMLNotifyEvent Index 37 Read GetHTMLEvent Write SetHTMLEvent; + Property OnMouseDown : THTMLNotifyEvent Index 38 Read GetHTMLEvent Write SetHTMLEvent; + Property OnMouseEnter : THTMLNotifyEvent Index 39 Read GetHTMLEvent Write SetHTMLEvent; + Property OnMouseLeave : THTMLNotifyEvent Index 40 Read GetHTMLEvent Write SetHTMLEvent; + Property OnMouseMove : THTMLNotifyEvent Index 41 Read GetHTMLEvent Write SetHTMLEvent; + Property OnMouseOut : THTMLNotifyEvent Index 42 Read GetHTMLEvent Write SetHTMLEvent; + Property OnMouseUp : THTMLNotifyEvent Index 43 Read GetHTMLEvent Write SetHTMLEvent; + Property OnOverFlow : THTMLNotifyEvent Index 44 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPaste : THTMLNotifyEvent Index 45 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPause : THTMLNotifyEvent Index 46 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPlay : THTMLNotifyEvent Index 47 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPointerCancel : THTMLNotifyEvent Index 48 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPointerDown : THTMLNotifyEvent Index 49 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPointerEnter : THTMLNotifyEvent Index 50 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPointerLeave : THTMLNotifyEvent Index 51 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPointerMove : THTMLNotifyEvent Index 52 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPointerOut : THTMLNotifyEvent Index 53 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPointerOver : THTMLNotifyEvent Index 54 Read GetHTMLEvent Write SetHTMLEvent; + Property OnPointerUp : THTMLNotifyEvent Index 55 Read GetHTMLEvent Write SetHTMLEvent; + Property OnReset : THTMLNotifyEvent Index 56 Read GetHTMLEvent Write SetHTMLEvent; + Property OnResize : THTMLNotifyEvent Index 57 Read GetHTMLEvent Write SetHTMLEvent; + Property OnScroll : THTMLNotifyEvent Index 58 Read GetHTMLEvent Write SetHTMLEvent; + Property OnSelect : THTMLNotifyEvent Index 59 Read GetHTMLEvent Write SetHTMLEvent; + Property OnSubmit : THTMLNotifyEvent Index 60 Read GetHTMLEvent Write SetHTMLEvent; + Property OnTouchStart : THTMLNotifyEvent Index 61 Read GetHTMLEvent Write SetHTMLEvent; + Property OnTransitionCancel : THTMLNotifyEvent Index 62 Read GetHTMLEvent Write SetHTMLEvent; + Property OnTransitionEnd : THTMLNotifyEvent Index 63 Read GetHTMLEvent Write SetHTMLEvent; + Property OnTransitionRun : THTMLNotifyEvent Index 64 Read GetHTMLEvent Write SetHTMLEvent; + Property OnTransitionStart : THTMLNotifyEvent Index 65 Read GetHTMLEvent Write SetHTMLEvent; + Property OnWheel : THTMLNotifyEvent Index 66 Read GetHTMLEvent Write SetHTMLEvent; + end; + TCustomWebWidgetClass = Class of TCustomWebWidget; + + { TWebWidget } + + TWebWidget = Class(TCustomWebWidget) + Published + // Properties + Property ParentID; + Property ElementID; + Property Classes; + Property Styles; + Property StyleRefresh; + Property Visible; + // Events + Property BeforeRenderHTML; + Property AfterRenderHTML; + Property OnAbort; + Property OnAnimationCancel; + Property OnAnimationEnd; + Property OnAnimationIteration; + Property OnAnimationStart; + Property OnAuxClick; + Property OnBlur; + Property OnCancel; + Property OnCanPlay; + Property OnCanPlayThrough; + Property OnChange; + Property OnClick; + Property OnCompositionEnd; + Property OnCompositionStart; + Property OnCompositionUpdate; + Property OnContextMenu; + Property OnCopy; + Property OnCut; + Property OnCueChange; + Property OnDblClick; + Property OnDurationChange; + Property OnEnded ; + Property OnError ; + Property OnFocus; + Property OnFocusIn ; + Property OnFocusOut ; + Property OnGotPointerCapture; + Property OnInput; + Property OnInvalid; + Property OnKeyDown; + Property OnKeyPress; + Property OnKeyUp; + Property OnLoad; + Property OnLoadedData; + Property OnLoadedMetaData; + Property OnLoadend; + Property OnLoadStart; + Property OnLostPointerCapture; + Property OnMouseDown; + Property OnMouseEnter; + Property OnMouseLeave; + Property OnMouseMove; + Property OnMouseOut; + Property OnMouseUp; + Property OnOverFlow; + Property OnPaste; + Property OnPause; + Property OnPlay; + Property OnPointerCancel; + Property OnPointerDown; + Property OnPointerEnter; + Property OnPointerLeave; + Property OnPointerMove; + Property OnPointerOut; + Property OnPointerOver; + Property OnPointerUp; + Property OnReset; + Property OnResize; + Property OnScroll; + Property OnSelect; + Property OnSubmit; + Property OnTouchStart; + Property OnTransitionCancel; + Property OnTransitionEnd; + Property OnTransitionRun; + Property OnTransitionStart; + Property OnWheel; + end; + TWebWidgetClass = Class of TWebWidget; + + { TContainerWidget } + + TContainerWidget = Class(TWebWidget) + private + const KnownStyleCount = 6; + Const KnownStyles : Array[0..KnownStyleCount] of string = ('min-width','max-width','min-height','max-height','display','width','height'); + function GetKnownStyle(AIndex: Integer): String; + procedure SetKnownStyle(AIndex: Integer; AValue: String); + Public + Function HTMLTag : String; override; + Constructor Create(aOwner : TComponent); override; + Property MinWidth : String Index 0 Read GetKnownStyle Write SetKnownStyle; + Property MaxWidth : String Index 1 Read GetKnownStyle Write SetKnownStyle; + Property MinHeight : String Index 2 Read GetKnownStyle Write SetKnownStyle; + Property MaxHeight : String Index 3 Read GetKnownStyle Write SetKnownStyle; + Property Display : String Index 4 Read GetKnownStyle Write SetKnownStyle; + Property Width : String Index 5 Read GetKnownStyle Write SetKnownStyle; + Property Height : String Index 6 Read GetKnownStyle Write SetKnownStyle; + end; + + { TCustomTemplateWidget } + + TCustomTemplateWidget = Class(TWebWidget) + private + FContainerTag: String; + FTemplate: TStrings; + procedure DoTemplateChanged(Sender: TObject); + procedure SetContainerTag(AValue: String); + procedure SetTemplate(AValue: TStrings); + Protected + function GetTemplateHTML: String; virtual; + Procedure ApplyTemplate(aElement : TJSHTMLElement); virtual; + Function DoRenderHTML(aParent, aElement: TJSHTMLElement) : TJSHTMLElement; override; + // The template. + Property Template : TStrings Read FTemplate Write SetTemplate; + // When set, a tag will be created and the template will be rendered below this tag. + Property ContainerTag : String Read FContainerTag Write SetContainerTag; + Public + Function HTMLTag : String; override; + Constructor Create(aOwner : TComponent); override; + Destructor Destroy; override; + end; + +implementation + +ResourceString + SErrCannotSetParentAndElementID = 'ElementID and ParentID cannot be set at the same time.'; + SErrCannotRenderWithoutParent = 'Cannot render without parent'; + SErrInvalidChildIndex = 'Invalid child index: value %d is not in valid range [0..%d]'; + SErrUnknownStyle = 'Unknown style: %s'; + SErrElementIDNotAllowed = 'Setting element ID is not allowed'; + SErrParentIDNotAllowed = 'Setting parent ID is not allowed'; + SErrParentNotAllowed = 'Setting parent is not allowed'; + SErrWidgetNotFound = 'Widget with ID "%s" not found.'; + +{ TWebWidgetReferences } + +function TWebWidgetReferences.GetReferenceItem(aIndex : Integer): TReferenceItem; +begin + +end; + +procedure TWebWidgetReferences.SetReferenceItem(aIndex : Integer; AValue: TReferenceItem); +begin + +end; + +procedure TWebWidgetReferences.MarkDirty(aItem: TReferenceItem); +begin + if Assigned(Widget) and Assigned(Widget.Element) then + RefreshFromDOM(aItem,Widget.Element) +end; + +procedure TWebWidgetReferences.RefreshFromDOM(aItem: TReferenceItem; aElement: TJSHTMlElement); +begin + +end; + +function TWebWidgetReferences.Widget: TCustomWebWidget; +begin + Result:=TCustomWebWidget(owner); +end; + +function TWebWidgetReferences.Add(const aName: String; aSelector: String): TReferenceItem; +begin + Result:=Add as TReferenceItem; + Result.FName:=aName; + Result.Selector:=aSelector; + if (aSelector<>'') then + MarkDirty(Result) +end; + +function TWebWidgetReferences.EnsureReference(const aName: String): TReferenceItem; +begin + +end; + +function TWebWidgetReferences.IndexOfReference(const aName: String): TReferenceItem; +begin + +end; + +function TWebWidgetReferences.FindReference(const aName: String): TReferenceItem; +begin + +end; + +function TWebWidgetReferences.GetReference(const aName: String): TReferenceItem; +begin + +end; + +procedure TWebWidgetReferences.RemoveReference(const aName: String); +begin + +end; + +function TWebWidgetReferences.GetElementByName(const aName: String): TJSHTMLElement; +begin + +end; + +function TWebWidgetReferences.GetElementsByName(const aName: String): TJSHTMLElementArray; +begin + +end; + +procedure TWebWidgetReferences.RefreshFromDOM(aElement: TJSHTMlElement); +begin + +end; + +{ TReferenceItem } + +procedure TReferenceItem.SetName(AValue: String); +begin + if FName=AValue then Exit; + FName:=AValue; + MarkDirty; +end; + +function TReferenceItem.GetElement: TJSHTMLElement; +begin + if Assigned(Collection) then + Result:=(Collection as TWebWidgetReferences).GetElementByName(Name) + else + Result:=Nil; +end; + +function TReferenceItem.GetElements: TJSHTMLElementArray; +begin + if Assigned(Collection) then + Result:=(Collection as TWebWidgetReferences).GetElementsByName(Name) + else + Result:=Nil; +end; + +procedure TReferenceItem.MarkDirty; +begin + if Assigned(Collection) then + (Collection as TWebWidgetReferences).MarkDirty(Self); +end; + +procedure TReferenceItem.Refresh; +begin + MarkDirty; +end; + +procedure TReferenceItem.SetSelector(AValue: String); +begin + if FSelector=AValue then Exit; + FSelector:=AValue; + MarkDirty; +end; + +{ TCustomTemplateWidget } + +procedure TCustomTemplateWidget.SetTemplate(AValue: TStrings); +begin + if FTemplate=AValue then Exit; + FTemplate.Assign(AValue); +end; + +function TCustomTemplateWidget.GetTemplateHTML: String; +begin + Result:=FTemplate.Text; +end; + +procedure TCustomTemplateWidget.ApplyTemplate(aElement: TJSHTMLElement); +begin + aElement.InnerHTML:=GetTemplateHTML; +end; + +function TCustomTemplateWidget.DoRenderHTML(aParent, aElement: TJSHTMLElement): TJSHTMLElement; +begin + if (ContainerTag='') then + begin + ApplyTemplate(AParent); + Result:= TJSHTMLElement(aParent.firstElementChild); + end + else + begin + ApplyTemplate(aElement); + Result:=aElement; + end; +end; + +function TCustomTemplateWidget.HTMLTag: String; +begin + Result:=ContainerTag; +end; + +procedure TCustomTemplateWidget.DoTemplateChanged(Sender: TObject); +begin + if isRendered then + Refresh; +end; + +procedure TCustomTemplateWidget.SetContainerTag(AValue: String); +begin + if FContainerTag=AValue then Exit; + FContainerTag:=AValue; + if IsRendered then + Refresh; +end; + +constructor TCustomTemplateWidget.Create(aOwner: TComponent); + +begin + inherited Create(aOwner); + FTemplate:=TStringList.Create; + TStringList(FTemplate).OnChange:=@DoTemplateChanged; + FContainerTag:=''; +end; + +destructor TCustomTemplateWidget.Destroy; + +begin + FreeAndNil(FTemplate); + inherited Destroy; +end; + +{ TContainerWidget } + +function TContainerWidget.GetKnownStyle(AIndex: Integer): String; + +var + S : TStyleItem; +begin + S:=Styles.FindStyle(KnownStyles[aIndex]); + if Assigned(S) then + Result:=S.Value; +end; + +procedure TContainerWidget.SetKnownStyle(AIndex: Integer; AValue: String); + +begin + Styles.EnsureStyle(KnownStyles[aIndex]).Value:=aValue; +end; + +function TContainerWidget.HTMLTag: String; +begin + Result:='div'; +end; + +constructor TContainerWidget.Create(aOwner: TComponent); +begin + inherited Create(aOwner); + MinWidth:='32px'; + MinHeight:='12px'; + Width:='50%'; + Height:='50%'; +end; + + + +{ TWebWidgetStyles } + +function TWebWidgetStyles.GetStyleItem(aIndex: Integer): TStyleItem; +begin + Result:=TStyleItem(Items[Aindex]); +end; + +procedure TWebWidgetStyles.SetItem(aIndex : Integer; AValue: TStyleItem); +begin + Items[aIndex]:=aValue; +end; + +procedure TWebWidgetStyles.MarkDirty(aItem: TStyleItem); + +Var + El : TJSHTMLElement; + +begin + If Assigned(Widget) then + begin + el:=Widget.Element; + if Assigned(El) then + ApplyToDom(El,aItem); + end; +end; + +procedure TWebWidgetStyles.ApplyToDOM(aElement: TJSHTMlElement; aItem: TStyleItem); + +Const + Prios : Array[TStylePriority] of string = ('','important'); + +begin + With AItem do + if (Name<>'') then + if (Value<>'') then + aElement.Style.setProperty(Name,Value,Prios[Priority]) + else + aElement.Style.removeProperty(name); +end; + +function TWebWidgetStyles.Add(const aName: String; const aValue: String): TStyleItem; +begin + Result:=Add as TStyleItem; + // Don't use Name + Result.FName:=aName; + if aValue<>'' then + Result.Value:=aValue; // will trigger markdirty +end; + +function TWebWidgetStyles.EnsureStyle(const aName: String; const aValue: String): TStyleItem; +begin + Result:=FindStyle(aName); + if Result=Nil then + Result:=Add(aName,aValue) + else if AValue<>'' then + Result.Value:=aValue +end; + +function TWebWidgetStyles.Widget: TCustomWebWidget; +begin + Result:=TCustomWebWidget(Owner); +end; + +function TWebWidgetStyles.IndexOfStyle(const aName: String): integer; + +begin + Result:=Count-1; + While (Result>=0) and not SameText(aName,GetStyleItem(Result).Name) do + Dec(Result); +end; + +function TWebWidgetStyles.FindStyle(const aName: String): TStyleItem; + +Var + Idx : integer; + +begin + Idx:=IndexOfStyle(aName); + If Idx=-1 then + Result:=Nil + else + Result:=GetStyleItem(Idx) +end; + +function TWebWidgetStyles.GetStyle(const aName: String): TStyleItem; +begin + Result:=FindStyle(aName); + if Result=Nil then + Raise EWidgets.CreateFmt(SErrUnknownStyle,[aName]); +end; + +function TWebWidgetStyles.RemoveStyle(const aName: String): String; + +Var + I : Integer; + el : TJSHTMLElement; + +begin + I:=IndexOfStyle(aName); + if I<>-1 then + begin + Result:=Styles[i].Value; + Delete(I); + end; + if Assigned(Widget) then + begin + el:=Widget.Element; + if Assigned(el) then + begin + if (Result='') then + Result:=el.style.getPropertyValue(aName); + el.style.removeProperty(aName); + end; + end; +end; + +procedure TWebWidgetStyles.RefreshFromDOM(aElement : TJSHTMlElement = Nil;DoClear: Boolean = False); + +Var + S : TJSCSSStyleDeclaration; + I : integer; + K : String; + W : TCustomWebWidget; + itm : TStyleItem; + +begin + if aElement= Nil then + begin + W:=Widget; + if assigned(W) then + aElement:=W.Element; + if AElement=Nil then exit; + end; + if DoClear then + Clear; + S:=aElement.style; + For I:=0 to S.length-1 do + begin + K:=S.Item(I); + itm:=FindStyle(K); + if Itm=Nil then + begin + Itm:=Add(K); + Itm.FImported:=True; + end; + Itm.FValue:=S.getPropertyValue(K); + Case LowerCase(S.getPropertyPriority(K)) of + 'important' : Itm.FPriority:=spImportant; + end; + end; +end; + +procedure TWebWidgetStyles.ClearImported; + +Var + I : integer; + +begin + I:=Count-1; + While I>=0 do + begin + If GetStyleItem(I).Fimported then + Delete(I); + Dec(I); + end; +end; + +procedure TWebWidgetStyles.ApplyToDOM(aElement : TJSHTMlElement = Nil); + +Var + I : Integer; + +begin + if (AElement=Nil) and (Widget<>Nil) then + aElement:=Widget.Element; + if AElement<>Nil then + For I:=0 to Count-1 do + ApplyToDOM(aElement,GetStyleItem(i)); +end; + +{ TStyleItem } + +procedure TStyleItem.MarkDirty; + +begin + If Assigned(Collection) then + TWebWidgetStyles(Collection).MarkDirty(Self); +end; + +procedure TStyleItem.SetValue(AValue: String); +begin + if FValue=AValue then Exit; + FValue:=aValue; + MarkDirty; +end; + +procedure TStyleItem.SetPriority(AValue: TStylePriority); +begin + if FPriority=AValue then Exit; + FPriority:=AValue; + MarkDirty; +end; + + +procedure TStyleItem.SetName(AValue: String); +begin + if aValue=FName then Exit; + FName:=AValue; + MarkDirty; +end; + +procedure TStyleItem.Assign(Source: TPersistent); + +Var + SI : TStyleItem; + +begin + if Source is TStyleItem then + begin + SI:=Source as TStyleItem; + FName:=SI.FName; + FValue:=SI.FValue; + FImported:=SI.FImported; + MarkDirty; + end + else + inherited Assign(Source); +end; + + +{ TCustomWebWidget } + +function TCustomWebWidget.DisplayElementName: String; + +begin + Result:=Name; + If Result='' then + Result:=' <'+HTMLTag+'>'; + if Assigned(FElement) then + Result:=Result+'#'+FElement.ID; + Result:=Result+' (Type: '+ClassName+')'; +end; + +function TCustomWebWidget.EnsureElement : TJSHTMLElement; + +var + P : TJSHTMLElement; + +begin + Result:=GetElement; + if Result=Nil then + begin + // If we have a parent, make sure it has it's element + if Assigned(Parent) then + Parent.EnsureElement; + P:=ParentElement; + if (P=Nil) and (FixedElement=Nil) then + Raise EWidgets.CreateFmt(SErrCannotRenderWithoutParent,[DisplayElementName]) + else + begin + Result:=RenderHTML(P); + FOwnsElement:=True; + FElement:=Result; + ApplyData; + end; + end; +end; + +procedure TCustomWebWidget.InvalidateParentElement; +begin + FParentElement:=nil; +end; + +procedure TCustomWebWidget.InvalidateElement; +begin + If FStyles.Count>0 then + FStyles.ClearImported; + FElement:=nil; +end; + +function TCustomWebWidget.WidgetClasses: String; +begin + Result:=''; +end; + + +function TCustomWebWidget.GetElement: TJSHTMLELement; + +Var + El : TJSHTMLElement; + + +begin + if (FElement=Nil) then + begin + if (FElementID<>'') then + begin + El:=FindElement(FElementID); + if Assigned(El) then + ApplyWidgetSettings(el); + FElement:=El; + ApplyData; + end; + end; + Result:=FElement; +end; + +function TCustomWebWidget.GetExternalElement: Boolean; +begin + Result:=(FElementID<>'') +end; + +function TCustomWebWidget.GetHaveReferences: Boolean; +begin + Result:=Assigned(FReferences); +end; + +function TCustomWebWidget.GetHTMLEvent(AIndex: Integer): THTMLNotifyEvent; + +Var + Fun : JSValue; +begin + Result:=nil; + if Assigned(FMyEvents) and (aIndex>=0) and (aIndex<=MaxEvents) then + begin + Fun:=FMyEvents[FEventNames[aindex]]; + if Not isUndefined(Fun) then + Result:=THTMLNotifyEvent(Fun); + end; +end; + +function TCustomWebWidget.GetIsElementDirty: Boolean; +begin + Result:=IsRendered; +end; + +function TCustomWebWidget.GetClasses: String; +begin + if IsRendered Then + FClasses:=FElement.ClassName; + Result:=FClasses; +end; + +function TCustomWebWidget.GetDataset(aName : String): String; + +Var + el : TJSHTMLElement; + +begin + el:=Element; + if Assigned(El) then + Result:=String(El.Dataset[aName]) + else + Result:=''; +end; + +function TCustomWebWidget.GetChildCount: Integer; +begin + Result:=FChildren.Length; +end; + +function TCustomWebWidget.GetChild(aIndex : Integer): TCustomWebWidget; +begin + if (aIndex<0) or (aIndex>=FChildren.Length) then + Raise EListError.CreateFmt(SErrInvalidChildIndex,[aIndex,FChildren.Length-1]); + Result:=TCustomWebWidget(FChildren[aIndex]); +end; + +function TCustomWebWidget.GetContentElement: TJSHTMLELement; +begin + Result:=Element; +end; + +function TCustomWebWidget.GetParent: TCustomWebWidget; +begin + Result:=FParent; +end; + +function TCustomWebWidget.GetParentElement: TJSHTMLELement; + +Var + El : TJSHTMLElement; + +begin + if (FParentElement=Nil) then + begin + El:=TopElement; + if Assigned(el) then + FParentElement:=TJSHTMLElement(el.parentElement) + else if (FParentID<>'') then + FParentElement:=FindElement(FParentID) + else if Assigned(FParent) then + FParentElement:=FParent.ContentElement + else + FParentElement:=DefaultParentElement; + end; + Result:=FParentElement; +end; + +function TCustomWebWidget.GetParentID: String; + +Var + E : TJSHTMLElement; + +begin + Result:=''; + E:=ParentElement; + if Assigned(E) then + Result:=E.ID + else + Result:=FParentID; +end; + +function TCustomWebWidget.GetElementID: String; + +Var + El : TJSHTMLElement; + +begin + El:=Element; + If Assigned(El) then + Result:=el.ID + else + Result:=FElementID; +end; + +function TCustomWebWidget.GetReferences: TWebWidgetReferences; +begin + if (FReferences=Nil) then + FReferences:=CreateReferences; + Result:=FReferences; + +end; + +function TCustomWebWidget.GetRendered: Boolean; +begin + Result:=(FElement<>Nil) +end; + +function TCustomWebWidget.GetTopElement: TJSHTMLELement; +begin + Result:=Element; +end; + +function TCustomWebWidget.GetVisible: Boolean; +begin + Result:=FVisible; +end; + +procedure TCustomWebWidget.SetClasses(AValue: String); +begin + FClasses:=AddClasses(AValue,WidgetClasses); + If IsRendered then + FElement.ClassName:=FClasses; +end; + +procedure TCustomWebWidget.SetDataset(aName : String; AValue: String); + +Var + El : TJSHTMLElement; + +begin + el:=Element; + If (El=Nil) then + Raise EWidgets.Create(SErrNotRendered); + el.Dataset[aName]:=aValue; +end; + +procedure TCustomWebWidget.SetElementID(AValue: String); +begin + if (FElementID=AValue) then Exit; + if (aValue<>'') then + begin + if (FParentID<>'') then + Raise EWidgets.Create(SErrCannotSetParentAndElementID); + if FixedElement<>Nil then + Raise EWidgets.Create(SErrElementIDNotAllowed); + FElementID:=AValue; + end + else + begin + FElementID:=AValue; + if IsRendered then + Unrender(ParentElement); + end; +end; + +procedure TCustomWebWidget.SetHTMLEvent(AIndex: Integer; AValue: THTMLNotifyEvent); + +Var + EventName : String; + +begin + if (aIndex<0) or (aIndex>MaxEvents) then + exit; + EventName:=FEventNames[aIndex]; + if Assigned(aValue) then + AddEvent(EventName,AValue) + else + DeleteEvent(EventName); +end; + +procedure TCustomWebWidget.SetParent(AValue: TCustomWebWidget); + +Var + ReRender : Boolean; +begin + if (AValue=FParent) then exit; + if (FixedParent<>Nil) then + Raise EWidgets.Create(SErrParentNotAllowed); + If Assigned(FParent) then + FParent.RemoveChild(Self); + // Unrender + ReRender:=IsRendered; + if ReRender then + UnRender(ParentElement); + InvalidateParentElement; + If Assigned(aValue) then + begin + FParentID:=''; + aValue.AddChild(Self); // Sets FParent + end; + if ReRender and Assigned(ParentElement) then + begin + FElement:=RenderHTML(ParentElement); + if Assigned(FElement) then + ApplyData; + end; +end; + +procedure TCustomWebWidget.SetParentID(AValue: String); + +Var + ReRender : Boolean; + +begin + if (FParentID=AValue) then exit; + if (aValue<>'') then + begin + if (FElementID<>'') then + Raise EWidgets.Create(SErrCannotSetParentAndElementID); + if (FixedParent<>Nil) then + Raise EWidgets.Create(SErrParentIDNotAllowed); + end; + ReRender:=IsRendered; + if ReRender then + UnRender(ParentElement); + if (aValue<>'') and Assigned(FParent) then + FParent.RemoveChild(Self); + FParentID:=aValue; + InvalidateParentElement; + if ReRender and Assigned(ParentElement) then + EnsureElement; +end; + +procedure TCustomWebWidget.AddChild(aValue: TCustomWebWidget); +begin + if AValue=Nil then exit; + aValue.FParent:=Self; + if FChildren.IndexOf(aValue)=-1 then + FChildren.Push(aValue); +end; + +procedure TCustomWebWidget.RemoveChild(aValue: TCustomWebWidget); + +Var + I : NativeInt; + +begin + if AValue=Nil then exit; + I:=FChildren.indexOf(aValue); + if I>=0 then + begin + FChildren.splice(I,1); + aValue.FParent:=Nil; + end; +end; + +procedure TCustomWebWidget.SetReferences(AValue: TWebWidgetReferences); +begin + if (aValue=FReferences) then exit; + References.Assign(aValue); + if IsRendered then + References.RefreshFromDOM(FElement); +end; + +procedure TCustomWebWidget.SetStyles(AValue: TWebWidgetStyles); +begin + if FStyles=AValue then Exit; + FStyles.Assign(AValue); +end; + +procedure TCustomWebWidget.SetVisible(AValue: Boolean); + +Var + el : TJSHTMLElement; + +begin + if aValue=FVisible then + Exit; + el:=Element; + if Assigned(el) then + ApplyVisible(el,aValue) + else + FVisible:=aValue; +end; + +procedure TCustomWebWidget.ApplyVisible(aElement: TJSHTMLElement;AValue: Boolean); + +begin + if aValue then + begin + if (FDisplay<>'') then + aElement.Style.setProperty('display',FDisplay) + else + aElement.Style.removeProperty('display'); + end + else + begin + FDisplay:=aElement.Style.getPropertyValue('display'); + aElement.Style.setProperty('display','none'); + end; + FVisible:=aValue; +end; + +procedure TCustomWebWidget.EventEntry(aEvent: TJSEvent); + +Var + R : TEventDispatch; + Fun : JSValue; + +begin + R.MsgStr:=aEvent._type; + R.HTMLEvent:=aEvent; + if Assigned(FMyEvents) then + Fun:=FMyEvents[R.MsgStr] + else + Fun:=nil; + if Not (isUndefined(Fun) or isNull(Fun)) then + R.EventHandler:=THTMLNotifyEvent(Fun); + DispatchStr(R); + if (R.EventHandler<>Nil) then + R.EventHandler(Self,R.HTMLEvent); +end; + +function TCustomWebWidget.CreateStyles: TWebWidgetStyles; +begin + Result:=TWebWidgetStyles.Create(Self,TStyleItem); +end; + +function TCustomWebWidget.CreateReferences: TWebWidgetReferences; +begin + Result:=TWebWidgetReferences.Create(Self,TReferenceItem); +end; + +procedure TCustomWebWidget.RemoveEvent(aElement: TJSHTMLElement; const aEvent: String); +begin + aElement.RemoveEventListener(aEvent,FMyHook); +end; + +procedure TCustomWebWidget.HookupEvent(aElement: TJSHTMLElement; const aEvent : String); + +begin + aElement.addEventListener(aEvent,FMyHook); +end; + +procedure TCustomWebWidget.HookupEvents(aElement: TJSHTMLElement); + +Var + Event : String; + +begin + if Assigned(FMyEvents) then + for Event in TJSObject.keys(FMyEvents) do + HookupEvent(aElement,Event); +end; + +procedure TCustomWebWidget.AddEvent(aName: String; AHandler: THTMLNotifyEvent); + +Var + el : TJSHTMLElement; + +begin + if FMyEvents=nil then + FMyEvents:=TJSObject.New; + FMyEvents[aName]:=aHandler; + El:=Element; + if Assigned(El) then + HookupEvent(el,aName); +end; + +procedure TCustomWebWidget.DeleteEvent(aName: String); + +Var + el : TJSHTMLElement; + +begin + if (FMyEvents<>nil) and FMyEvents.hasOwnProperty(aName) then + JSDelete(FMyEvents,aName); + El:=Element; + if Assigned(El) then + RemoveEvent(el,aName); +end; + +class function TCustomWebWidget.FixedParent: TJSHTMLElement; +begin + Result:=Nil; +end; + +class function TCustomWebWidget.DefaultParentElement: TJSHTMLElement; +begin + Result:=Nil; +end; + +class function TCustomWebWidget.DefaultParent: TCustomWebWidget; +begin + Result:=nil; +end; + +class function TCustomWebWidget.FixedElement: TJSHTMLElement; +begin + Result:=Nil; +end; + + + +class function TCustomWebWidget.FindElement(aID: String): TJSHTMLElement; +begin + Result:=TJSHTMLElement(Document.getElementbyID(aID)); +end; + +class function TCustomWebWidget.CreateElement(aTag: String; aID: String + ): TJSHTMLElement; +begin + Result:=TJSHTMLElement(Document.createElement(aTag)); + if aID<>'' then + Result.id:=aID; +end; + +procedure TCustomWebWidget.Refresh; + +Var + P : TJSHTMLElement; + +begin + P:=Nil; + if Assigned(FElement) then + begin + P:=ParentElement; + if Assigned(P) and (FElementID='') then // Do not remove when it's not ours to begin with + P.removeChild(FElement); + end; + InvalidateParentElement; + InvalidateElement; + EnsureElement; +end; + +procedure TCustomWebWidget.ApplyWidgetSettings(aElement: TJSHTMLElement); + +// Normally, this should be called BEFORE FElement is set. +// But we'll be extra careful, and not rely on getters using FElement. + +Var + S : String; + +begin + if aElement.id='' then + aElement.id:=GenerateID; + // Don't use Classes, it will return FElement.Classname when set + S:=AddClasses(FClasses,WidgetClasses); + if (S<>'') then + AddClasses(aElement,S); + if FStyles.Count>0 then + FStyles.ApplyToDOM(aElement); + if Not FVisible then + ApplyVisible(aElement,FVisible); + // Maybe we should put this under control of a property or so ? + // TStyleRefresh = (srAlways,srOnElementID,srNever) + if (StyleRefresh = srAlways) + or ((FelementID<>'') and (FElementID<>'')) then + FStyles.RefreshFromDom(aElement,False); +end; + +function TCustomWebWidget.DoRenderHTML(aParent, aElement: TJSHTMLElement): TJSHTMLElement; +begin + If AParent=Nil then + Console.Log(DisplayElementName+': render without parent!'); + Result:=aElement; +end; + + +procedure TCustomWebWidget.ApplyData; + +Var + AID : String; + + Procedure MaybeSet(El : TJSHTMLElement; AName : String); + + begin + if Assigned(el) then + el.Dataset[aName]:=AID; + end; + +begin + AID:=ElementID; + if assigned(Element) then + Element.dataset[SElementClass]:=ClassName; + MaybeSet(Element,SElementData); + MaybeSet(TopElement,STopElementData); + if AllowChildren then + MaybeSet(ContentElement,SContentElementData); +end; + +procedure TCustomWebWidget.RemoveData; + + Procedure MaybeUnSet(El : TJSHTMLElement; AName : String); + + begin + if Assigned(el) then + jsDelete(el.Dataset,aName); + end; + +begin + MaybeUnSet(Element,SElementData); + MaybeUnSet(TopElement,STopElementData); + MaybeUnSet(ContentElement,SContentElementData); +end; + +class function TCustomWebWidget.GenerateID: String; + +begin + Inc(WidgetID); + Result:='ww-'+intToStr(WidgetID); +end; + +function TCustomWebWidget.RenderHTML(aParent: TJSHTMLELement): TJSHTMLElement; + +Var + aTag : String; + +begin + aTag:=HTMLTag; + if aTag='' then + Result:=Nil + else + begin + Result:=FixedElement; + if Result=Nil then + Result:=CreateElement(HTMLTag,GenerateID); + end; + if Assigned(Result) and Assigned(aParent) then + aParent.AppendChild(Result); + if Assigned(FBeforeRenderHTML) then + FBeforeRenderHTML(Self); + Result:=DoRenderHTML(aParent,Result); + if Assigned(Result) then + begin + ApplyWidgetSettings(Result); + HookupEvents(Result); + end; + if Assigned(FAfterRenderHTML) then + FAfterRenderHTML(Self); +end; + +procedure TCustomWebWidget.DoUnRender(aParent: TJSHTMLElement); + +begin + if Assigned(aParent) and Assigned(FElement) then + begin + if FOwnsElement then + aParent.removeChild(FElement); + FElement:=Nil + end; +end; + +procedure TCustomWebWidget.UnRender(aParent: TJSHTMLElement); +begin + if Assigned(FBeforeUnRenderHTML) then + FBeforeUnRenderHTML(Self); + RemoveData; + DoUnRender(aParent); + if Assigned(FAfterUnRenderHTML) then + FAfterUnRenderHTML(Self); +end; + +function TCustomWebWidget.DispatchEvent(aName: String; aEvent: TJSEvent): Boolean; + +begin + if not IsRendered then + exit; + if (aEvent=Nil) then + aEvent:=TJSEvent.New(aName); + Result:=Element.dispatchEvent(aEvent); +end; + +constructor TCustomWebWidget.Create(aOwner: TComponent); +begin + inherited Create(aOwner); + FChildren:=TJSArray.New; + FStyles:=CreateStyles; + FMyHook:=@EventEntry; + FParent:=DefaultParent; + FVisible:=True; +end; + +destructor TCustomWebWidget.Destroy; + +Var + I : integer; + C : TCustomWebWidget; + +begin + For I:=0 to FChildren.Length-1 do + begin + C:=TCustomWebWidget(FChildren[i]); + FChildren[i]:=Nil; + C.Free; + end; + Parent:=Nil; + ParentID:=''; + FChildren:=Nil; + FreeAndNil(FStyles); + inherited Destroy; +end; + +class function TCustomWebWidget.AllowChildren: Boolean; +begin + Result:=True; +end; + +class function TCustomWebWidget.RemoveClasses(const Source, aClasses: String; Normalize : Boolean = false): String; + +var + T : TJSStringDynArray; + i : integer; + S : String; +begin + Result:=Source; + if Normalize then + Result:=TJSString(Result).replace(TJSRegexp.New('\s\s+','g'),' '); + T:=TJSString(Result).split(' '); + For S in TJSString(aClasses).split(' ') do + if (S<>'') then + begin + I:=TJSArray(T).indexOf(S); + if (I<>-1) then + TJSArray(T).splice(i,1); + end; + Result:=TJSArray(T).join(' '); +end; + +class function TCustomWebWidget.RemoveClasses(el: TJSHTMLElement; const aClasses: String; Normalize : Boolean = false): String; + +begin + Result:=RemoveClasses(el.ClassName,aClasses,Normalize); + el.ClassName:=Result; +end; + +class function TCustomWebWidget.AddClasses(const Source, aClasses: String; Normalize : Boolean = false): String; + +var + T : TJSStringDynArray; + S : String; + +begin + Result:=Source; + if Normalize then + Result:=TJSString(Result).replace(TJSRegexp.New('\s\s+','g'),' '); + if AClasses='' then exit; + T:=TJSString(Result).split(' '); + For S in TJSString(aClasses).split(' ') do + if (S<>'') then + begin + if (TJSArray(T).indexOf(S)=-1) then + TJSArray(T).Push(S); + end; + Result:=TJSArray(T).Join(' '); +end; + +class function TCustomWebWidget.AddClasses(el: TJSHTMLElement; const aClasses: String; Normalize : Boolean = false): String; + +begin + Result:=AddClasses(el.ClassName,aClasses,Normalize); + el.ClassName:=Trim(Result); +end; + +function TCustomWebWidget.RemoveClasses(const aClasses: String; Normalize : Boolean = false): String; +begin + Result:=RemoveClasses(FClasses,aClasses,Normalize); + if IsRendered then + Result:=RemoveClasses(FElement,aClasses,Normalize) +end; + +function TCustomWebWidget.AddClasses(const aClasses: String; Normalize: Boolean): String; +begin + Result:=AddClasses(FClasses,aClasses,Normalize); + if IsRendered then + Result:=AddClasses(FElement,aClasses,Normalize) +end; + +function TCustomWebWidget.FindWidgetByID(aElementID: String; Recurse: Boolean): TCustomWebWidget; + +Var + I : Integer; + +begin + Result:=Nil; + if aElementID='' then + exit; + if (aElementID=elementID) then + Exit(Self); + I:=ChildCount-1; + // First this level. Typical layout is not so nested. + While (i>=0) and (Result=Nil) do + begin + Result:=Children[i]; + if (Result.ElementID<>aElementID) then + Result:=nil; + Dec(I); + end; + If (Result=Nil) and (Recurse) then + begin + I:=ChildCount-1; + While (i>=0) and (Result=Nil) do + begin + Result:=Children[i].FindWidgetByID(aElementID,True); + Dec(I); + end; + end; +end; + +function TCustomWebWidget.GetWidgetByID(aElementID: String; Recurse: Boolean): TCustomWebWidget; +begin + Result:=FindWidgetByID(aElementID,Recurse); + if Result=Nil then + Raise EWidgets.CreateFmt(SErrWidgetNotFound,[aElementID]); +end; + +function TCustomWebWidget.EnsureStyle(const aName: String): TStyleItem; +begin + Result:=Styles.EnsureStyle(aName); +end; + +function TCustomWebWidget.AddStyle(const aName, aValue: String): TStyleItem; +begin + Result:=EnsureStyle(aName); + Result.Value:=aValue; +end; + +function TCustomWebWidget.GetStyleValue(const aName : String): String; + +Var + S : TStyleItem; + +begin + S:=Styles.FindStyle(aName); + if Assigned(S) then + Result:=S.Value + else + Result:=''; +end; + +function TCustomWebWidget.RemoveStyle(const aName: String): String; + +begin + Result:=Styles.RemoveStyle(aName); +end; + +procedure TCustomWebWidget.RemoveData(const aName: String); +begin + if IsRendered then + jsDelete(Element.Dataset,aName) +end; + + + +end. +