Browse Source

* Webwidget embryonal version

michael 6 years ago
parent
commit
eab2006134
32 changed files with 10571 additions and 0 deletions
  1. 52 0
      demo/webwidget/designdemo/design.css
  2. 29 0
      demo/webwidget/designdemo/designdemo.html
  3. 103 0
      demo/webwidget/designdemo/designdemo.lpi
  4. 35 0
      demo/webwidget/designdemo/designdemo.lpr
  5. 353 0
      demo/webwidget/designdemo/designer.pp
  6. 232 0
      demo/webwidget/designdemo/webideclient.pp
  7. BIN
      demo/webwidget/designdemo/widgets/button.png
  8. BIN
      demo/webwidget/designdemo/widgets/checkbox.png
  9. BIN
      demo/webwidget/designdemo/widgets/container.png
  10. BIN
      demo/webwidget/designdemo/widgets/edit.png
  11. BIN
      demo/webwidget/designdemo/widgets/image.png
  12. BIN
      demo/webwidget/designdemo/widgets/jumbo.png
  13. BIN
      demo/webwidget/designdemo/widgets/memo.png
  14. BIN
      demo/webwidget/designdemo/widgets/radio.png
  15. BIN
      demo/webwidget/designdemo/widgets/select.png
  16. 1553 0
      demo/webwidget/nativedesign/frmmain.lfm
  17. 556 0
      demo/webwidget/nativedesign/frmmain.pp
  18. BIN
      demo/webwidget/nativedesign/nativedesigner.ico
  19. 91 0
      demo/webwidget/nativedesign/nativedesigner.lpi
  20. 22 0
      demo/webwidget/nativedesign/nativedesigner.lpr
  21. BIN
      demo/webwidget/nativedesign/nativedesigner.res
  22. 817 0
      demo/webwidget/nativedesign/webideintf.pp
  23. 1587 0
      packages/webwidget/htmlwidgets.pp
  24. 49 0
      packages/webwidget/lazwebwidgets.lpk
  25. 15 0
      packages/webwidget/lazwebwidgets.pas
  26. 42 0
      packages/webwidget/tests/btnrun.pp
  27. 1155 0
      packages/webwidget/tests/tchtmlwidgets.pp
  28. 1760 0
      packages/webwidget/tests/tcwidget.pp
  29. 19 0
      packages/webwidget/tests/testwidgets.html
  30. 108 0
      packages/webwidget/tests/testwidgets.lpi
  31. 16 0
      packages/webwidget/tests/testwidgets.lpr
  32. 1977 0
      packages/webwidget/webwidget.pas

+ 52 - 0
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;
+}

+ 29 - 0
demo/webwidget/designdemo/designdemo.html

@@ -0,0 +1,29 @@
+<!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">
+  <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"> 
+  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
+  <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">
+  <link rel="stylesheet" href="design.css">
+  <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
+  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
+  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
+  <title>Designdemo</title>
+  <script src="designdemo.js"></script>
+</head>
+<body>
+  <div id="toolbar"></div>
+  <div id="designpage"></div>
+  <div class="source">
+      Created using &nbsp; <a target="_blank" href="https://wiki.freepascal.org/pas2js">pas2js.</a>
+     &nbsp;&nbsp;Sources: &nbsp; <a target="new" href="designdemo.lpr">Program</a> &nbsp;
+     <a target="new" href="designer.pp">unit</a>.
+
+  </div>
+  <script>
+  window.addEventListener("load", rtl.run);
+  </script>
+</body>
+</html>

+ 103 - 0
demo/webwidget/designdemo/designdemo.lpi

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <Runnable Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="designdemo"/>
+      <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>

+ 35 - 0
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.

+ 353 - 0
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.
+

+ 232 - 0
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.
+

BIN
demo/webwidget/designdemo/widgets/button.png


BIN
demo/webwidget/designdemo/widgets/checkbox.png


BIN
demo/webwidget/designdemo/widgets/container.png


BIN
demo/webwidget/designdemo/widgets/edit.png


BIN
demo/webwidget/designdemo/widgets/image.png


BIN
demo/webwidget/designdemo/widgets/jumbo.png


BIN
demo/webwidget/designdemo/widgets/memo.png


BIN
demo/webwidget/designdemo/widgets/radio.png


BIN
demo/webwidget/designdemo/widgets/select.png


+ 1553 - 0
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

+ 556 - 0
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.
+

BIN
demo/webwidget/nativedesign/nativedesigner.ico


+ 91 - 0
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>

+ 22 - 0
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.
+

BIN
demo/webwidget/nativedesign/nativedesigner.res


+ 817 - 0
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.
+

+ 1587 - 0
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.
+

+ 49 - 0
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>

+ 15 - 0
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.

+ 42 - 0
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.
+

+ 1155 - 0
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.
+

+ 1760 - 0
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.
+

+ 19 - 0
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</title>
+  <script src="testwidgets.js"></script>
+</head>
+<body>
+  <button class="btn btn-default" id="RunTest">Run tests</button>
+  <div id="fpcunit-controller"></div>
+  <div id="fpcunit"></div>
+  <div id="widget-window"></div>
+  <div id="pasjsconsole"></div>
+  <script>
+    rtl.run();
+  </script>
+</body>
+</html>

+ 108 - 0
packages/webwidget/tests/testwidgets.lpi

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <Runnable Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="testwidgets"/>
+      <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>

+ 16 - 0
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.

+ 1977 - 0
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.
+