{***************************************************************************}
{ TMS IntraWeb Component Pack Pro                                           }
{ for Delphi & C++Builder                                                   }
{                                                                           }
{ written by TMS Software                                                   }
{            copyright © 2010 - 2014                                        }
{            Email : info@tmssoftware.com                                   }
{            Web : http://www.tmssoftware.com                               }
{                                                                           }
{ The source code is given as is. The author is not responsible             }
{ for any possible damage done due to the use of this code.                 }
{ The component can be freely used in any application. The complete         }
{ source code remains property of the author and may not be distributed,    }
{ published, given or sold in any form as such. No parts of the source      }
{ code can be included in any other component or application without        }
{ written authorization of the author.                                      }
{***************************************************************************}

unit IWAdvTreeView;

interface

{$I TMSDEFS.INC}

{$R tmsiwadvtreeview.RES}

uses
  Windows, Graphics, Controls, IWPicCntnr, Forms,
  {$IFDEF DELPHI6_LVL}
  Types, StrUtils,
  {$ENDIF}
  Classes, IWControl, SysUtils, IWHTMLTag, IWTMSBase, AdvTreeNodes,
  IWRenderContext, IWColor, IWFont, IWXMLTag;

type

  TTIWAdvTreeView = class;

  TItemClickEvent = procedure(Sender: TComponent; Node: TAdvTreeNode) of object;
  TIWAsyncItemClickEvent = procedure(Sender: TObject; EventParams: TStringList; Node: TAdvTreeNode) of object;

  TAdvTreeViewClientEvents = class(TPersistent)
  private
    FItemClick: TStringList;

    procedure SetItemClick(const Value: TStringList);
  protected
  public
    constructor Create;
    destructor Destroy; override;
    function GetJSFunction(PropName: string): string;
  published
    property ItemClick: TStringList read FItemClick write SetItemClick;
  end;

  TTIWAdvTreeView = class(TIWControl)
  private
    hiddenvalue: string;
    itlevel: integer;
    getitemindex: integer;
    itemindex: integer;
    htmltv: string;
    csstv: string;
    itemcount: integer;
    setvaluedata: string;

    FAutoChildCheck: boolean;
    FBGColor: TIWColor;
    FClientEvents: TAdvTreeViewClientEvents;
    FFont: TIWFont;
    FHotColor: TIWColor;
    FScrollToSelectedNode: boolean;
    FSelectedColor: TIWColor;
    FSelectedFontColor: TIWColor;
    FSelectedIndex: integer;
    FSelectedNode: TAdvTreeNode;
    FCheckedNode: TAdvTreeNode;
    FCheckedIndex: integer;
    FIncludeJQueryLibrary: boolean;
    FItems: TAdvTreeNodes;
    FNodeImageURL: string;
    FNodeLineImageURL: string;

    FOnItemClick: TItemClickEvent;
    FOnCheckBoxClick: TItemClickEvent;
    FOnAsyncItemClick: TIWAsyncItemClickEvent;
    FOnAsyncCheckBoxClick: TIWAsyncItemClickEvent;

    procedure DoAsyncItemClick(AParams: TStringList);
    procedure DoAsyncCheckBoxClick(AParams: TStringList);
    procedure SetAutoChildCheck(const Value: boolean);
    procedure SetBGColor(const Value: TIWColor);
    procedure SetFont(const Value: TIWFont);
    procedure SetHotColor(const Value: TIWColor);
    procedure SetScrollToSelectedNode(const Value: boolean);
    procedure SetSelectedColor(const Value: TIWColor);
    procedure SetSelectedFontColor(const Value: TIWColor);
    procedure SetSelectedNode(const Value: TAdvTreeNode);

  protected
    procedure SetValue(const AValue: string); override;
    procedure Submit(const AValue: string); override;
    procedure UpdateSelectedIndex();
    procedure UpdateSelectedIndexChildNodes(Node: TAdvTreeNode);
    function MakeTreeView(): string;
    function MakeTreeNode(Node: TAdvTreeNode): string;
    function AddChildNodes(Node: TAdvTreeNode): string;
    function RetrieveChildNodes(Node: TAdvTreeNode): string;
    function MakeCSS(Node: TAdvTreeNode): string;
    function get_ShouldRenderTabOrder: Boolean; override;
    procedure NodesChanged(Sender: TObject);
  public
    property SelectedNode: TAdvTreeNode read FSelectedNode write SetSelectedNode;
    function get_HasTabOrder: boolean; override;
    procedure IWPaint; override;
    function RenderHTML(AContext: TIWBaseComponentContext): TIWHTMLTag; override;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function RenderAsync(AContext: TIWBaseHTMLComponentContext): TIWXMLTag; override;
  published
    property AutoChildCheck: boolean read FAutoChildCheck write SetAutoChildCheck default false;
    property BGColor: TIWColor read FBGColor write SetBGColor default clWebWhite;
    property ClientEvents: TAdvTreeViewClientEvents read FClientEvents write FClientEvents;
    property Font: TIWFont read FFont write SetFont;
    property HotColor: TIWColor read FHotColor write SetHotColor default clWebRed;
    property ScrollToSelectedNode: boolean read FScrollToSelectedNode write SetScrollToSelectedNode default false;
    property SelectedColor: TIWColor read FSelectedColor write SetSelectedColor default $FFEAD9;
    property SelectedFontColor: TIWColor read FSelectedFontColor write SetSelectedFontColor default clWebRed;
    property IncludeJQueryLibrary: boolean read FIncludeJQueryLibrary write FIncludeJQueryLibrary default true;
    property Items: TAdvTreeNodes read FItems write FItems;
    property NodeImageURL: string read FNodeImageURL write FNodeImageURL;
    property NodeLineImageURL: string read FNodeLineImageURL write FNodeLineImageURL;

    property OnItemClick: TItemClickEvent read FOnItemClick write FOnItemClick;
    property OnCheckBoxClick: TItemClickEvent read FOnCheckBoxClick write FOnCheckBoxClick;
    property OnAsyncItemClick: TIWAsyncItemClickEvent read FOnAsyncItemClick write FOnAsyncItemClick;
    property OnAsyncCheckBoxClick: TIWAsyncItemClickEvent read FOnAsyncCheckBoxClick write FOnAsyncCheckBoxClick;
  end;

implementation

uses
  ImgList, ShellAPI, CommCtrl,
  IWApplication, IWServerControllerBase, TMSIWStrings, TMSIWSystem, IWServer, IWAppForm;


{ TTIWAdvTreeView }
procedure TTIWAdvTreeView.SetAutoChildCheck(const Value: boolean);
begin
  FAutoChildCheck := Value;
  Invalidate;
end;

procedure TTIWAdvTreeView.SetBGColor(const Value: TIWColor);
begin
  FBGColor := Value;
  Invalidate;
end;

procedure TTIWAdvTreeView.SetFont(const Value: TIWFont);
begin
  FFont := Value;
  Invalidate;
end;

procedure TTIWAdvTreeView.SetHotColor(const Value: TIWColor);
begin
  FHotColor := Value;
  Invalidate;
end;

procedure TTIWAdvTreeView.SetScrollToSelectedNode(const Value: boolean);
begin
  FScrollToSelectedNode := Value;
  Invalidate;
end;

procedure TTIWAdvTreeView.SetSelectedColor(const Value: TIWColor);
begin
  FSelectedColor := Value;
  Invalidate;
end;

procedure TTIWAdvTreeView.SetSelectedFontColor(const Value: TIWColor);
begin
  FSelectedFontColor := Value;
  Invalidate;
end;

procedure TTIWAdvTreeView.SetSelectedNode(const Value: TAdvTreeNode);
begin
  FSelectedNode := Value;
  if FSelectedNode = nil then
    FSelectedIndex := -1;
  Invalidate;
end;

procedure TTIWAdvTreeView.DoAsyncItemClick(AParams: TStringList);
begin
  if Assigned(OnAsyncItemClick) and not (SelectedNode = nil) then
    OnAsyncItemClick(Self, AParams, SelectedNode);
end;

procedure TTIWAdvTreeView.DoAsyncCheckBoxClick(AParams: TStringList);
begin
  if Assigned(OnAsyncCheckBoxClick) and not (FCheckedNode = nil) then
    OnAsyncCheckBoxClick(Self, AParams, FCheckedNode);
end;

function TTIWAdvTreeView.RenderAsync(
  AContext: TIWBaseHTMLComponentContext): TIWXMLTag;
var
  js, html: string;
begin
  Result := TIWXMLTag.CreateTag('control');
  try
    Result.AddStringParam('id', HTMLName);
    Result.AddStringParam('type', 'TTIWADVTREEVIEW');
    RenderAsyncCommonProperties(AContext, Result);

    html := StringReplace(MakeTreeView(), '\', '\\', [rfReplaceAll]);
    html := StringReplace(html, '"', '\"', [rfReplaceAll]);
    html := StringReplace(html, #$D, '', [rfReplaceAll]);
    html := StringReplace(html, #13, '', [rfReplaceAll]);
    html := StringReplace(html, #10, '', [rfReplaceAll]);
    html := StringReplace(html, #9, '', [rfReplaceAll]);

    js :=
    'var myel = document.getElementById("' + HTMLName + 'tvdiv");'
    + 'if (myel)'
    + '  myel.innerHTML = "' + html + '";'
    + '$("#' + HTMLName + 'tv").treeview();'
    + 'var myhi = FindElem("' + HTMLName + '_INPUT");'
    + 'if (myhi)'
    + ' myhi.value = "' + hiddenvalue + '|' + IntToStr(FSelectedIndex) + '";';

    GGetWebApplicationThreadVar.CallBackResponse.AddJavaScriptToExecute('<![CDATA[' + js + ']]>');

  except
    FreeAndNil(Result);
  end;
end;

constructor TTIWAdvTreeView.Create(AOwner: TComponent);
begin
  inherited;
  {$IFNDEF TMSIW121}
  SetRenderSize(true);
  {$ENDIF}
  FNeedsFormTag := True;

  FAutoChildCheck := false;
  FBGColor := clWhite;
  FClientEvents := TAdvTreeViewClientEvents.Create;
  FFont := TIWFont.Create;
  FHotColor := clWebRed;
  FIncludeJQueryLibrary := true;
  FItems := TAdvTreeNodes.Create(self);
  FItems.OnChange := NodesChanged;
  FSelectedColor := $FFEAD9;
  FSelectedFontColor := clWebRed;
  FSelectedIndex := -1;
  FCheckedIndex := -1;
  FSelectedNode := nil;
  FCheckedNode := nil;
  itemcount := -1;
  Height := 250;
  Width := 200;

  if not (csDesigning in Componentstate) then
  begin
//    TIWServer.AddInternalFile('IW_JS_TMSJQUERY','/js/TMSJQUERY.js');
//    TIWServer.AddInternalFile('IW_JS_TMSJQUERYTREEVIEW','/js/TMSJQUERY.TREEVIEW.js');
    if IncludeJQueryLibrary then
      TMSAddInternalFile('IW_JS_TMSJQUERY','/js/TMSJQUERY.js');
//moved to initialization section
//    TMSAddInternalFile('IW_JS_TMSJQUERYTREEVIEW','/js/TMSJQUERY.TREEVIEW.js');
  end;
end;

destructor TTIWAdvTreeView.Destroy;
begin
  inherited;
  FClientEvents.Free;
  FFont.Free;
  FItems.Free;
end;

procedure TTIWAdvTreeView.NodesChanged(Sender: TObject);
begin
  Invalidate;
end;


function TTIWAdvTreeView.MakeTreeNode(Node: TAdvTreeNode): string;
var
  j: integer;
  isclosed, ischecked, imageclass, imageid, itemhint, parentid: string;
  parentNode: TAdvTreeNode;
begin
  Result := '';
  isclosed := '';

  if not Node.IsExpanded then
  begin
    isclosed := ' class="closed"';
    hiddenvalue := hiddenvalue + '0';
  end
  else
  begin
    hiddenvalue := hiddenvalue + '1';
  end;

  if Node.Level < itlevel then
  begin
    for j := Node.Level to itLevel - 1 do
      Result := Result + '</ul>'#13;
  end;

  if Node.Level > itlevel then
    Result := Result + '<ul>'#13;

  if Node.ImageURL = '' then
    imageclass := ''
  else
  begin
    imageid := StringReplace(Node.ImageURL,'/','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'.','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'_','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'-','',[rfReplaceAll]);
    imageid := StringReplace(imageid,':','',[rfReplaceAll]);
    imageclass := ' class="' + imageid + '"';
  end;

  if Node.Hint <> '' then
    itemhint := ' title="' + Node.Hint +'"'
  else
    itemhint := '';

  Result := Result
    + '<li' + isclosed
    + ' onClick="' + HTMLName + 'toggleNode(event, ' + IntToStr(itemindex) + ')"'
    + '>';

  if Node.URL <> '' then
    Result := Result + '<a href="' + Node.URL + '">';

//  if (Node = SelectedNode) then
//    FSelectedIndex := itemindex - 1;

  Result := Result
    + '<span id="' + HTMLName + IntToStr(itemindex) + '"'
//    + iif(FSelectedIndex = (itemindex-1), 'style="color:' + ColorToRGBString(SelectedFontColor)
    + iif(Node = SelectedNode, 'style="color:' + ColorToRGBString(SelectedFontColor)
      + ';background-color:' + ColorToRGBString(SelectedColor) + ';"', '')
    + itemhint + imageclass
    + ' onClick="' + HTMLName + 'toggleNode(event, ' + IntToStr(itemindex) + ')"'
    + ' onMouseOver="this.style.color = ''' + ColorToRGBString(HotColor) + ''';"'
    + ' onMouseOut="' + HTMLName + 'hoveredNode(this,' + IntToStr(itemindex) + ');"'
    + '>'#13;

  if Node.Checked then
  begin
    ischecked := ' checked';
    hiddenvalue := hiddenvalue + '1^'
  end
  else
  begin
    ischecked := '';
    hiddenvalue := hiddenvalue + '0^'
  end;

  if Node.NodeType = ntCheckBox then
    Result := Result + '<input id="' + IntToStr(itemindex) + 'cb" type="checkbox"' + ischecked + ' onclick="'
    + HTMLName + 'toggleCheckBox(event,' + IntToStr(itemindex) + ', this)">'
  else if Node.NodeType = ntRadioButton then
  begin
    parentid := '';
    ParentNode := Node.Parent;

    while (ParentNode <> nil) do
    begin
      parentid := parentid + IntToStr(ParentNode.Index);
      ParentNode := ParentNode.Parent;
    end;

    Result := Result + '<input type="radio" id="' + HTMLName + 'radio' + IntToStr(itemindex) + '" name="' + HTMLName + 'radiogroup'
    + parentid + '"' + ischecked + ' onclick="'
    + HTMLName + 'toggleRadio(event, this,' + IntToStr(itemindex) + ')">';
  end;

  Result := Result + Node.Text + '</span>'#13;

  if Node.URL <> '' then
    Result := Result + '</a>'#13;

  itlevel := Node.Level;
end;

procedure TTIWAdvTreeView.UpdateSelectedIndex();
var
  UN: TAdvTreeNode;
begin
  getitemindex := 0;

  UN := Items.GetFirstNode;
  while (UN <> nil) do
  begin
    if SelectedNode = UN then
    begin
      FSelectedIndex := getitemindex;
    end;

    getitemindex := getitemindex + 1;

    UpdateSelectedIndexChildNodes(UN);

    UN := UN.getNextSibling;
  end;
  getitemindex := 0;
end;

procedure TTIWAdvTreeView.UpdateSelectedIndexChildNodes(Node: TAdvTreeNode);
var
  UExN: TAdvTreeNode;
begin
  if not Assigned(Node) then
    Exit;

  UExN := Node.getFirstChild;
  if Assigned(UExN) then
  begin
    while (UExN <> nil) do
    begin
      if SelectedNode = UExN then
      begin
        FSelectedIndex := getitemindex;
      end;

      getitemindex := getitemindex + 1;
      UpdateSelectedIndexChildNodes(UExN);
      UExN := UExN.getNextSibling;
    end;
  end;
end;

function TTIWAdvTreeView.MakeTreeView(): string;
var
  htmlres: string;
  N: TAdvTreeNode;
  j: integer;
begin

  UpdateSelectedIndex;

  htmlres := htmlres + ''#13
  	+ '<ul id="' + HTMLName + 'tv" class="filetree">'#13;

  itlevel := 0;
  itemindex := 0;
  hiddenvalue := '';
  htmltv := '';
  csstv := '';

  N := Items.GetFirstNode;
  while (N <> nil) do
  begin
    itemindex := itemindex + 1;

    htmlres := htmlres + MakeTreeNode(N);

    AddChildNodes(N);

    htmlres := htmlres + htmltv;
    htmltv := '';
    csstv := '';

    N := N.getNextSibling;
  end;

  itemcount := itemindex;
  itemindex := 0;

  for j := 0 to itLevel do
    htmlres := htmlres + '</ul>'#13;

    Result := htmlres;
end;

function TTIWAdvTreeView.AddChildNodes(Node: TAdvTreeNode): string;
var
  ExN: TAdvTreeNode;
begin
  if not Assigned(Node) then
    Exit;

  ExN := Node.getFirstChild;
  if Assigned(ExN) then
  begin
    while (ExN <> nil) do
    begin
      if FSelectedIndex = itemindex then
        SelectedNode := ExN;

      itemindex := itemindex + 1;
      htmltv := htmltv + MakeTreeNode(ExN);
      csstv := csstv + MakeCSS(ExN);
      AddChildNodes(ExN);
      ExN := ExN.getNextSibling;
    end;
  end;
end;


procedure TTIWAdvTreeView.IWPaint;
var
  N: TAdvTreeNode;
  i: integer;
begin
  with Canvas do
  begin
    Brush.Color := BGColor;
    Pen.Color := Font.Color;
    Rectangle(0,0,Width - 1,Height - 1);

    i := 0;

    if Items.Count > 0 then
    begin
      N := Items.GetFirstNode;
      while (N <> nil) do
      begin
        TextOut(8,(6 + (12 * i)),N.Text);
        i := i + 1;
        N := N.getNextSibling;
      end;
    end
    else
      TextOut(8,6,Name);
  end;
end;

function TTIWAdvTreeView.get_HasTabOrder: Boolean;
begin
  Result := true;
end;

function TTIWAdvTreeView.get_ShouldRenderTabOrder: boolean;
begin
  Result := false;
end;

function TTIWAdvTreeView.RetrieveChildNodes(Node: TAdvTreeNode): string;
var
  ExN: TAdvTreeNode;
  vi: integer;
  si, sc: string;
begin
  if not Assigned(Node) then
    Exit;

  ExN := Node.getFirstChild;
  if Assigned(ExN) then
  begin
    while (ExN <> nil) do
    begin
      if FSelectedIndex = itemindex then
        SelectedNode := ExN;

      if FCheckedIndex = itemindex then
        FCheckedNode := ExN;

      itemindex := itemindex + 1;

      vi := pos('^', setvaluedata);
      if vi > 0 then
      begin
        si := copy(setvaluedata, 1, vi - 2);
        sc := copy(setvaluedata, 2, vi - 2);
        Delete(setvaluedata, 1, vi);

        if (StrToInt(si) = 1) then
          ExN.IsExpanded := True
        else
          ExN.IsExpanded := false;

        if (ExN.NodeType = ntCheckBox) or (ExN.NodeType = ntRadioButton) then
        begin
          if (StrToInt(sc) = 1) then
            ExN.Checked := True
          else
            ExN.Checked := false;
        end;
      end;

      RetrieveChildNodes(ExN);
      ExN := ExN.getNextSibling;
    end;
  end;
end;

procedure TTIWAdvTreeView.SetValue(const AValue: string);
var
  VarVal, su, si, sc, itsu: string;
  vp, vi: integer;
  N: TAdvTreeNode;
begin
  inherited;

  VarVal := AValue;

  vp := pos('|', VarVal);
  if vp > 0 then
  begin

    su := copy(VarVal, 0, vp - 1);
//    itsu := copy(VarVal, vp + 1, Length(VarVal));
    itsu := copy(VarVal, vp + 1, pos('~', VarVal) - (vp + 1));
    Delete(VarVal, 0, vp);

    if pos('~CB', VarVal) > 0 then
    begin
      FCheckedNode := nil;
      FCheckedIndex := StrToInt(itsu);
      FSelectedIndex := -1;
    end
    else
    begin
      //if no nodes have been selected the value is still empty
      if itsu <> '' then
        FSelectedIndex := StrToInt(itsu);
      FCheckedIndex := -1;
    end;

    setvaluedata := su;

    N := Items.GetFirstNode;
    while (N <> nil) do
    begin

      if FSelectedIndex = itemindex then
          SelectedNode := N;
      if FCheckedIndex = itemindex then
          FCheckedNode := N;

      itemindex := itemindex + 1;

      vi := pos('^', setvaluedata);
      if vi > 0 then
      begin
        si := copy(setvaluedata, 1, vi - 2);
        sc := copy(setvaluedata, 2, vi - 2);
        Delete(setvaluedata, 1, vi);

        if (StrToInt(si) = 1) then
          N.IsExpanded := True
        else
          N.IsExpanded := false;

        if (N.NodeType = ntCheckBox) or (N.NodeType = ntRadioButton) then
        begin
          if (StrToInt(sc) = 1) then
            N.Checked := True
          else
            N.Checked := false;
        end;
      end;

      RetrieveChildNodes(N);

      N := N.getNextSibling;
    end;
  end;
end;

procedure TTIWAdvTreeView.Submit(const AValue: string);
var
  VarVal: string;
  vp: integer;
begin
  inherited;

  VarVal := AValue;

  vp := pos('ITEMCLICK', VarVal);
  if vp > 0 then
  begin

    //SelectedIndex and SelectedNode are updated in SetValue
    //vi := pos('#', VarVal);
    //si := copy(VarVal, vi + 1, Length(VarVal));
    //FSelectedIndex := StrToInt(si);

    if Assigned(OnItemClick) and not (SelectedNode = nil) then
      OnItemClick(Self, SelectedNode);
  end;

  vp := pos('CHECKBOXCLICK', VarVal);
  if vp > 0 then
  begin
    if Assigned(OnCheckBoxClick) and not (FCheckedNode = nil) then
      OnCheckBoxClick(Self, FCheckedNode);
  end;
end;

function TTIWAdvTreeView.MakeCSS(Node: TAdvTreeNode): string;
var
  imageid: string;
begin
  Result := '';

  if not (Node.ImageCollapsedURL = '') then
  begin
    imageid := StringReplace(Node.ImageURL,'/','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'.','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'_','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'-','',[rfReplaceAll]);
    imageid := StringReplace(imageid,':','',[rfReplaceAll]);

    Result := Result + '.filetree li.expandable span.' + imageid
      + ' { background: url(' + Node.ImageCollapsedURL + ') 0 0 no-repeat; padding: 1px 3px 1px 19px;}'#13
      ;
  end;

  if not (Node.ImageURL = '') then
  begin
    imageid := StringReplace(Node.ImageURL,'/','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'.','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'_','',[rfReplaceAll]);
    imageid := StringReplace(imageid,'-','',[rfReplaceAll]);
    imageid := StringReplace(imageid,':','',[rfReplaceAll]);

    Result := Result + '.filetree span.' + imageid
      + ' { background: url(' + Node.ImageURL + ') 0 0 no-repeat; padding: 1px 3px 1px 19px;}'#13
      ;
  end;
end;

function TTIWAdvTreeView.RenderHTML(AContext: TIWBaseComponentContext): TIWHTMLTag;
var
  htmlres, startupscript, submitclick, submitclick2, ajaxcall, ajaxcall2, imgpath, imgA, imgB : string;
  N: TAdvTreeNode;
begin

  if IsLibrary then
    imgpath := 'files/'
  else
    imgpath := '/files/';

  if NodeImageURL = EmptyStr then
    imgA := imgpath + 'treeview-default.gif'
  else
    imgA := NodeImageURL;

  if NodeLineImageURL = EmptyStr then
    imgB := imgpath + 'treeview-default-line.gif'
  else
    imgB := NodeLineImageURL;

  startupscript :=
	'$("#' + HTMLName + 'tv").treeview({'#13
	  + '});'#13
    + ''#13
    ;

  if ScrollToSelectedNode then
  begin
    startupscript := startupscript
      + HTMLName + 'el = document.getElementById("' + HTMLName + IntToStr(FSelectedIndex + 1) + '");'#13
      + 'if (' + HTMLName + 'el)'#13
      + HTMLName + 'el.scrollIntoView();'#13
      ;
  end;

  TIWComponent40Context(AContext).AddToInitProc(startupscript + EOL);

  with AContext.PageContext as TIWPageContext40 do
  begin
    if IncludeJQueryLibrary then
      TIWComponent40Context(AContext).AddScriptFile('/js/TMSJQUERY.js');
    TIWComponent40Context(AContext).AddScriptFile('/js/TMSJQUERYTREEVIEW.js');
  end;

  if Assigned(OnItemClick) then
    submitclick := 'return SubmitClick('#39 + HTMLName + #39','#39'ITEMCLICK#'#39' + (index - 1),false);'#13
  else
    submitclick := '';

  if Assigned(OnCheckBoxClick) then
    submitclick2 := 'return SubmitClick('#39 + HTMLName + #39','#39'CHECKBOXCLICK#'#39' + (index - 1),false);'#13
  else
    submitclick2 := '';

  if Assigned(FOnAsyncItemClick) then
  begin
    ajaxcall := 'processAjaxEvent(''onClick'', '
      + HTMLControlImplementation.IWCLName
      + ',''' + HTMLName + '.' + 'DoAsyncItemClick' + ''','
      + 'true' + ', null, '
      + 'true' + ');';

      Acontext.WebApplication.RegisterCallBack(HTMLName+'.DoAsyncItemClick', DoAsyncItemClick);

      submitclick := '';
  end;

  if Assigned(FOnAsyncCheckBoxClick) then
  begin
    ajaxcall2 := 'processAjaxEvent(''onCheckBoxClick'', '
      + HTMLControlImplementation.IWCLName
      + ',''' + HTMLName + '.' + 'DoAsyncCheckBoxClick' + ''','
      + 'true' + ', null, '
      + 'true' + ');';

      Acontext.WebApplication.RegisterCallBack(HTMLName+'.DoAsyncCheckBoxClick', DoAsyncCheckBoxClick);

      submitclick2 := '';
  end;

  htmlres := '<style>'
    + '.'+HTMLName+'CSS .treeview, .'+HTMLName+'CSS .treeview ul {'#13
    + '	padding: 0;'#13
    + '	margin: 0;'#13
    + ' color: ' + ColorToRGBString(Font.Color) + ';'#13
    + iif(fsBold in Font.Style, 'font-weight:bold;', '')
    + ' font: ' + Font.FontToStringStyle(AContext.WebApplication.Browser) + ';'#13
    + '}'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview ul {'#13
    + '	background-color: ' + ColorToRGBString(BGColor) + ';'#13
    + '	margin-top: 4px;'#13
    + '}'#13
    + ''#13
    + '.'+HTMLName+'CSS ul li {'#13
    + '	list-style: none!important;'#13
    + '	list-style-type: none!important;'#13
    + '}'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview .hitarea {'#13
    + '	background: url(' + imgA + ') -64px -25px no-repeat;'#13
    + '	height: 16px;'#13
    + '	width: 16px;'#13
    + '	margin-left: -16px;'#13
    + '	float: left;'#13
    + '	cursor: pointer;'#13
    + '}'#13
    + '/* fix for IE6 */'#13
    + '* html .hitarea {'#13
    + '	display: inline;'#13
    + '	float:none;'#13
    + '}'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview li {'#13
    + '	margin: 0;'#13
    + '	padding: 3px 0pt 3px 16px;'#13
    + '}'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview a.selected {'#13
    + '	background-color: #eee;'#13
    + '}'#13
    + ''#13
    + '#treecontrol { margin: 1em 0; display: none; }'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview .hover { color: ' + ColorToRGBString(HotColor) + '; cursor: pointer; }'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview li { background: url(' + imgB + ') 0 0 no-repeat; }'#13
    + '.'+HTMLName+'CSS .treeview li.collapsable, .'+HTMLName+'CSS .treeview li.expandable { background-position: 0 -176px; }'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview .expandable-hitarea { background-position: -80px -3px; }'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview li.last { background-position: 0 -1766px }'#13
    + '.'+HTMLName+'CSS .treeview li.lastCollapsable, .'+HTMLName+'CSS .treeview li.lastExpandable { background-image: url(' + imgA + '); }'#13
    + '.'+HTMLName+'CSS .treeview li.lastCollapsable { background-position: 0 -111px }'#13
    + '.'+HTMLName+'CSS .treeview li.lastExpandable { background-position: -32px -67px }'#13
    + ''#13
    + '.'+HTMLName+'CSS .treeview div.lastCollapsable-hitarea, .'+HTMLName+'CSS .treeview div.lastExpandable-hitarea { background-position: 0; }'#13

    + ''#13
//    + '.treeview-red li { background-image: url(/files/treeview-red-line.gif); }'#13
//    + '.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(/files/treeview-red.gif); }'#13
//    + ''#13
//    + '.treeview-black li { background-image: url(/files/treeview-black-line.gif); }'#13
//    + '.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(/files/treeview-black.gif); }'#13
//    + ''#13
//    + '.treeview-gray li { background-image: url(/files/treeview-gray-line.gif); }'#13
//    + '.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(/files/treeview-gray.gif); }'#13
//    + ''#13
//    + '.treeview-famfamfam li { background-image: url(/files/treeview-famfamfam-line.gif); }'#13
//    + '.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(/files/treeview-famfamfam.gif); }'#13
//    + ''#13
    + ''#13
    + '.filetree li { padding: 3px 0 2px 16px; }'#13
    + '.filetree span { padding: 1px 3px 1px 3px; cursor:pointer; }'#13
//    + '.filetree li.expandable span.folder { background: url(/files/folder-closed.gif) 0 0 no-repeat; }'#13
//    + '.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; }'#13
//    + '.filetree span.folder { background: url(/files/folder.gif) 0 0 no-repeat; }'#13
//    + '.filetree span.file { background: url(/files/file.gif) 0 0 no-repeat; }'#13
    ;

  htmltv := '';
  csstv := '';

  N := Items.GetFirstNode;
  while (N <> nil) do
  begin
    htmlres := htmlres + MakeCSS(N);

    AddChildNodes(N);
    htmlres := htmlres + csstv;
    csstv := '';
    htmltv := '';

    N := N.getNextSibling;
  end;

  htmlres := htmlres
    + '</style>';

  htmlres := htmlres
    + '<script>'
    + 'function ' + HTMLName + 'toggleRadio(e, ctrl,index){'#13

    + ' stopPropagation(e);'#13

    + '  el = FindElem("'+HTMLName+'_INPUT");'#13

    + '  if (el){'#13
    + '    arDiv = el.value.split("|");'#13
    + '    arVal = arDiv[0].split("^");'#13
    + '    rb = document.getElementsByName(ctrl.name);'#13

    + '    if (rb){'#13
    + '      for (i = 0; i < rb.length; i++){'#13
    + '        rbid = parseInt(rb[i].id.replace("' + HTMLName + 'radio", ""));'#13
    + '        if (index == rbid) {'#13
    + '          if (arVal[index - 1] == "00")'#13
    + '            arVal[index - 1] = "01";'#13
    + '          else if (arVal[index - 1] == "10")'#13
    + '            arVal[index - 1] = "11";'#13
    + '        } else {'#13
    + '          if (arVal[rbid - 1] == "01")'#13
    + '            arVal[rbid - 1] = "00";'#13
    + '          else if (arVal[rbid - 1] == "11")'#13
    + '            arVal[rbid - 1] = "10";'#13
    + '        }'#13

    + '      }'#13
    + '      el.value = arVal.join("^") + "|" + arDiv[1] + "~RB";'#13
    + '    }'#13
    + '  }'#13
    + '}'#13

//    + 'function ' + HTMLName + 'updateVal(index){'#13
//    + '  el = FindElem("'+HTMLName+'_INPUT");'#13
//    + '  if (el){'#13
//    + '    arDiv = el.value.split("|");'#13
//    + '    arVal = arDiv[0].split("^");'#13
//    + '    if (arVal[index - 1] == "01")'#13
//    + '      arVal[index - 1] = "00";'#13
//    + '    else if (arVal[index - 1] == "11")'#13
//    + '      arVal[index - 1] = "10";'#13
//    + '    else if (arVal[index - 1] == "00")'#13
//    + '      arVal[index - 1] = "01";'#13
//    + '    else'#13
//    + '      arVal[index - 1] = "11";'#13
//    + '  el.value = arVal.join("^") + "|" + arDiv[1];'#13
//    + '  }'#13
//    + '}'#13

    //if a CheckBox is clicked, then only update the checkbox value
    //and not the node collapsed/expanded state
    + 'function ' + HTMLName + 'updateVal(index, val){'#13
    + '  el = FindElem("'+HTMLName+'_INPUT");'#13
    + '  if (el){'#13
    + '    arDiv = el.value.split("|");'#13
    + '    arVal = arDiv[0].split("^");'#13
    + '    if (arVal[index - 1] == "01")'#13
    + '      arVal[index - 1] = "0" + val;'#13
    + '    else if (arVal[index - 1] == "11")'#13
    + '      arVal[index - 1] = "1" + val;'#13
    + '    else if (arVal[index - 1] == "00")'#13
    + '      arVal[index - 1] = "0" + val;'#13
    + '    else'#13
    + '      arVal[index - 1] = "1" + val;'#13
    + '  el.value = arVal.join("^");'#13
    + '  }'#13
    + '}'#13

    + 'function ' + HTMLName + 'toggleCheckBox(e, index, ctrl){'#13
    + '  var val;'#13
    + '  if (ctrl.checked)'#13
    + '    val = 1;'#13
    + '  else'#13
    + '    val = 0;'#13
    ;

  if not AutoChildCheck then
  begin
    htmlres := htmlres + '  ' + HTMLName + 'updateVal(index, val);'#13;
  end
  else
  begin
    //also (un)check all child nodes
    htmlres := htmlres
      + ' nel = ctrl.parentNode.parentNode;'#13
      + ' cb = nel.getElementsByTagName("input");'#13
      + ' for (i = 0; i < cb.length; i++) {'#13
      + '   if (cb[i].type == "checkbox") {'#13
      + '     cb[i].checked = ctrl.checked;'#13
      + '     ' + HTMLName + 'updateVal(cb[i].id.replace("cb", ""), val);'#13
      + '   }'#13
      + ' }'#13
  end;

  htmlres := htmlres
    + ' FindElem("'+HTMLName+'_INPUT").value += "|" + (index - 1) + "~CB";'#13
    + ' stopPropagation(e);'#13

    + submitclick2 + #13
    + ajaxcall2 + #13
    + '}'#13

    + 'function stopPropagation(e)'#13
    + '{'#13
    + '    e = e||event;'#13
    + '    e.stopPropagation? e.stopPropagation() : e.cancelBubble = true;'#13
    + '}'#13

    + 'function ' + HTMLName + 'hoveredNode(ctrl, index){'#13
    + '  var oldColor = "' + ColorToRGBString(Font.Color) + '";'#13
    + '  el = FindElem("'+HTMLName+'_INPUT");'#13
    + '  if (el){'#13
    + '    arDiv = el.value.split("|");'#13
    + '    if (parseInt(arDiv[1]) == (parseInt(index)-1))'#13
    + '      oldColor = "' + ColorToRGBString(SelectedFontColor) + '";'#13
    + '  }'#13
    + '  ctrl.style.color = oldColor;'#13
    + '}'#13

    + 'function ' + HTMLName + 'toggleNode(e, index, ctrl){'#13
    + ' stopPropagation(e);'#13
    + '  el = FindElem("'+HTMLName+'_INPUT");'#13
    + '  if (el){'#13
    + '    arDiv = el.value.split("|");'#13
    + '    arVal = arDiv[0].split("^");'#13
    + '    if (arVal[index - 1] == "01")'#13
    + '      arVal[index - 1] = "11";'#13
    + '    else if (arVal[index - 1] == "11")'#13
    + '      arVal[index - 1] = "01";'#13
    + '    else if (arVal[index - 1] == "00")'#13
    + '      arVal[index - 1] = "10";'#13
    + '    else'#13
    + '      arVal[index - 1] = "00";'#13

    + '  prevEl = document.getElementById("' + HTMLName + '" + (parseInt(arDiv[1])+1) );'#13
    + '  if (prevEl){'#13
    + '    prevEl.style.backgroundColor = "";'#13
    + '    prevEl.style.color = "' + ColorToRGBString(Font.Color) + '";'#13
    + '  }'#13

    + '  selEl = document.getElementById("' + HTMLName + '" + index);'#13
    + '  if (selEl){'#13
    + '    selEl.style.backgroundColor = "' + ColorToRGBString(SelectedColor) + '";'#13
    + '    selEl.style.color = "' + ColorToRGBString(SelectedFontColor) + '";'#13
    + '  }'#13

    + '  el.value = arVal.join("^") + "|" + (index - 1) + "~IT";'#13
    ;

    if not (ClientEvents.ItemClick.Text = '') then
    begin
      htmlres := htmlres
      + 'var el = selEl;'#13
      + 'var value = selEl.innerHTML;'#13
      + ClientEvents.ItemClick.Text + #13;
    end;

    htmlres := htmlres
    + submitclick + #13
    + ajaxcall + #13

    + '  }'#13
    + '}'#13
    + '</script>'#13;

  htmlres := htmlres + '<div id="' + HTMLName + 'tvdiv">'
    + MakeTreeView()
    + '</div>';

  Result := TIWHTMLTag.CreateTag('DIV');
  Result.AddStringParam('style', 'background:' + ColorToRGBString(BGColor) + ';overflow:auto;width:' + IntToStr(Width)
  + 'px;height:' + IntToStr(Height) + 'px;');
  Result.Contents.AddText(htmlres);

  with Result.Contents.AddTag('INPUT') do
  begin
    AddStringParam('TYPE', 'hidden');
//    AddStringParam('TYPE', 'text');
    AddStringParam('NAME', HTMLName);
    AddStringParam('ID', HTMLName + '_INPUT');
    AddStringParam('VALUE', hiddenvalue + '|' + IntToStr(FSelectedIndex));
  end;

end;

{ TAdvTreeViewClientEvents }

constructor TAdvTreeViewClientEvents.Create;
begin
  inherited;
  FItemClick := TStringList.Create;
end;

destructor TAdvTreeViewClientEvents.Destroy;
begin
  FItemClick.Free;
  inherited;
end;

function TAdvTreeViewClientEvents.GetJSFunction(PropName: string): string;
begin
  if (PropName = 'ItemClick') then
    Result := 'ItemClick(el, value) // el = item object, value = item value';
end;

procedure TAdvTreeViewClientEvents.SetItemClick(const Value: TStringList);
begin
  FItemClick.Assign(Value);
end;

initialization

//  if not (csDesigning in Componentstate) then
//  begin
//    TIWServer.AddInternalFile('IW_JS_TMSJQUERY','/js/TMSJQUERY.js');
//    TIWServer.AddInternalFile('IW_JS_TMSJQUERYTREEVIEW','/js/TMSJQUERY.TREEVIEW.js');
//    if TTIWAdvTreeView.IncludeJQueryLibrary then
//      TMSAddInternalFile('IW_JS_TMSJQUERY','/js/TMSJQUERY.js');
    TMSAddInternalFile('IW_JS_TMSJQUERYTREEVIEW','/js/TMSJQUERY.TREEVIEW.js');
//  end;

end.
