unit Grid.Plugins;

interface

uses
  SysUtils, Math, DB, Web, JS,

  WEBLib.StdCtrls, WEBLib.DBCtrls, WEBLib.Grids, WEBLib.Lists,

  XData.Web.Dataset,

  Grid.Settings,
  Dataset.Plugins,
  Paginator.Plugins;

type
  TDBTableControl = TWebDBTableControl;
  TListControl = TWebListControl;
  TLabel = TWebLabel;

  TGridPlugin = class
  const
    SORTING_CLASS = 'sorting';
    SORTING_ASC_CLASS = 'sorting_asc';
    SORTING_DESC_CLASS = 'sorting_desc';
  private
    FGrid: TWebDBTableControl;
    FDatasetPlugin: TDatasetPlugin;
    FPaginatorPlugin: TPaginatorPlugin;
    FPaginationInfo: TLabel;
    FSortedItems: TSortedItems;
    FSortingChanged: Boolean;
    FFilterText: string;
    function IsOrdenableColumn(ACol: integer): Boolean;
    function HasColumnWithOrder: Boolean;
    function GetFieldByColumn(ACol: Integer): TField;
    function GetSortedItems: TSortedItems;
    function CalculateSortedItems: TSortedItems;
    function GetPageSize: Integer;
    function GetPageNumber: Integer;
    function GetFilterText: string;
    function CreateSettings: TGridSettings;
  private
    procedure InitGrid;
    procedure ConfigureHeaderElement(ACol: integer);
    procedure DatasetLoadCallback(ADatasetPlugin: TDatasetPlugin);
    procedure PaginatorItemClickCallback(APaginatorPlugin: TPaginatorPlugin);
  public
    constructor Create(AGrid: TDBTableControl; ADataset: TXDataWebDataSet;
      APaginator: TListControl; APaginationInfo: TLabel);
    destructor Destroy; override;
    procedure Load;
    procedure NextPage;
    procedure PreviousPage;
    procedure SetPageSize(APageSize: Integer; AutoReload: Boolean = False);
    procedure SetFilterText(AFilterText: string; AutoReload: Boolean = False);
    function IsLastPage: Boolean;
    property SortedItems: TSortedItems read GetSortedItems;
    property FilterText: string read GetFilterText;
    property PageSize: Integer read GetPageSize;
    property PageNumber: Integer read GetPageNumber;
  end;

implementation

{ TGridPlugin }

constructor TGridPlugin.Create(AGrid: TDBTableControl; ADataset: TXDataWebDataSet;
  APaginator: TListControl; APaginationInfo: TLabel);
begin
  FGrid := AGrid;
  FDatasetPlugin := TDatasetPlugin.Create(ADataset, @DatasetLoadCallback);
  FPaginatorPlugin := TPaginatorPlugin.Create(APaginator, @PaginatorItemClickCallback);
  FPaginationInfo := APaginationInfo;
  SetFilterText('');
  InitGrid;
  FSortingChanged := False;
  FSortedItems := CalculateSortedItems;
end;

function TGridPlugin.CreateSettings: TGridSettings;
begin
  Result := TGridSettings.Create(FGrid, SortedItems, FilterText);
end;

procedure TGridPlugin.DatasetLoadCallback(ADatasetPlugin: TDatasetPlugin);
var
  PageCount: integer;
  FirstRecordOfPage, LastRecordOfPage: integer;
begin
  PageCount := ADatasetPlugin.ServerRecordCount div ADatasetPlugin.PageSize;
  if (ADatasetPlugin.ServerRecordCount mod ADatasetPlugin.PageSize) > 0 then
    Inc(PageCount);
  FPaginatorPlugin.Init(ADatasetPlugin.PageNumber, PageCount);

  FirstRecordOfPage := Min(
    ((ADatasetPlugin.PageNumber - 1) * ADatasetPlugin.PageSize) + 1,
    ADatasetPlugin.ServerRecordCount
  );

  LastRecordOfPage := Min(
    ADatasetPlugin.PageNumber * ADatasetPlugin.PageSize,
    ADatasetPlugin.ServerRecordCount
  );

  FPaginationInfo.Caption := Format('Showing %d to %d of %d entries', [
    FirstRecordOfPage, LastRecordOfPage, ADatasetPlugin.ServerRecordCount
  ]);
end;

destructor TGridPlugin.Destroy;
begin
  FPaginatorPlugin.Free;
  FDatasetPlugin.Free;
  inherited;
end;

function TGridPlugin.GetFieldByColumn(ACol: Integer): TField;
var
  DataField: string;
begin
  DataField := FGrid.Columns[ACol].DataField;
  if (DataField = '') then
    Exit(nil);
  if FGrid.DataSource = nil then
    Exit(nil);
  if FGrid.DataSource.DataSet = nil then
    Exit(nil);
  Result := FGrid.DataSource.DataSet.FieldByName(DataField);
end;

function TGridPlugin.GetFilterText: string;
begin
  Result := FFilterText;
end;

function TGridPlugin.GetPageNumber: Integer;
begin
  Result := FDatasetPlugin.PageNumber;
end;

function TGridPlugin.GetPageSize: Integer;
begin
  Result := FDatasetPlugin.PageSize;
end;

function TGridPlugin.GetSortedItems: TSortedItems;
begin
  if FSortingChanged then
  begin
    FSortedItems := CalculateSortedItems;
    FSortingChanged := False;
  end;
  Result := FSortedItems;
end;

function TGridPlugin.HasColumnWithOrder: Boolean;
var
  I: Integer;
  CellElement: TJSElement;
begin
  Result := False;
  for I := 0 to FGrid.Columns.Count - 1 do
  begin
    if not IsOrdenableColumn(I) then
      Continue;

    CellElement := FGrid.CellElements[I, 0];

    Result := CellElement.classList.contains(SORTING_ASC_CLASS) or
              CellElement.classList.contains(SORTING_DESC_CLASS);

    if Result then
      Break;
  end;
end;

procedure TGridPlugin.InitGrid;
var
  I: integer;
begin
  for I := 0 to FGrid.Columns.Count - 1 do
    ConfigureHeaderElement(I);
end;

function TGridPlugin.IsLastPage: Boolean;
begin
  Result := FDatasetPlugin.IsLastPage;
end;

function TGridPlugin.IsOrdenableColumn(ACol: integer): Boolean;
var
  DataField: string;
  Field: TField;
begin
  DataField := FGrid.Columns[ACol].DataField;
  if (DataField = '') then
    Exit(False);
  if FGrid.DataSource = nil then
    Exit(False);
  if FGrid.DataSource.DataSet = nil then
    Exit(False);
  Field := FGrid.DataSource.DataSet.FieldByName(DataField);
  if Field = nil then
    Exit(False);
  Result := True;
end;

procedure TGridPlugin.Load;
var
  Settings: TGridSettings;
begin
  Settings := CreateSettings;
  try
    FDatasetPlugin.LoadDataset(Settings);
  finally
    Settings.Free;
  end;
end;

procedure TGridPlugin.NextPage;
begin
  if FDatasetPlugin.NextPage then
    Load;
end;

procedure TGridPlugin.PaginatorItemClickCallback(
  APaginatorPlugin: TPaginatorPlugin);
begin
  if APaginatorPlugin.ActivePage <> FDatasetPlugin.PageNumber then
  begin
    FDatasetPlugin.PageNumber := APaginatorPlugin.ActivePage;
    Load;
  end;
end;

procedure TGridPlugin.PreviousPage;
begin
  if FDatasetPlugin.PreviousPage then
    Load;
end;

procedure TGridPlugin.SetFilterText(AFilterText: string; AutoReload: boolean);
begin
  FFilterText := AFilterText;
  if FDatasetPlugin.PageSize > 0 then
    FDatasetPlugin.PageNumber := 1;
  if AutoReload then
    Load;
end;

procedure TGridPlugin.ConfigureHeaderElement(ACol: integer);
var
  Element: TJSHTMLElement;

  function HeaderClick(Event: TJSMouseEvent): boolean;
  var
    ClassList: TJSDOMTokenList;
    CurrentOrderClass, NewOrderClass: string;
    I: Integer;
  begin
    NewOrderClass := '';
    ClassList := FGrid.CellElements[ACol, 0].classList;
    if ClassList.contains(SORTING_ASC_CLASS) then
    begin
      CurrentOrderClass := SORTING_ASC_CLASS;
      NewOrderClass := SORTING_DESC_CLASS;
    end
    else if ClassList.contains(SORTING_DESC_CLASS) then
    begin
      CurrentOrderClass := SORTING_DESC_CLASS;
      NewOrderClass := SORTING_ASC_CLASS;
    end
    else if ClassList.contains(SORTING_CLASS) then
    begin
      CurrentOrderClass := SORTING_CLASS;
      NewOrderClass := SORTING_ASC_CLASS;
    end;

    if NewOrderClass <> '' then
    begin
      ClassList.replace(CurrentOrderClass, NewOrderClass);

      for I := 0 to FGrid.Columns.Count - 1 do
      begin
        if I = ACol then
          Continue;
        if FGrid.CellElements[I, 0].classList.contains(SORTING_ASC_CLASS) then
          FGrid.CellElements[I, 0].classList.replace(SORTING_ASC_CLASS, SORTING_CLASS)
        else if FGrid.CellElements[I, 0].classList.contains(SORTING_DESC_CLASS) then
          FGrid.CellElements[I, 0].classList.replace(SORTING_DESC_CLASS, SORTING_CLASS);
      end;

      FSortedItems := CalculateSortedItems;
      FSortingChanged := True;

      Load;
    end;

    Result := True;
  end;
begin

  if IsOrdenableColumn(ACol) then
  begin
    Element := FGrid.CellElements[ACol, 0];
    Element.onclick := @HeaderClick;
    Element.style.setProperty('cursor', 'pointer');

    if not HasColumnWithOrder then
    begin
      if not Element.classList.contains(SORTING_ASC_CLASS) then
        Element.classList.add(SORTING_ASC_CLASS);
    end
    else
    begin
      if not Element.classList.contains(SORTING_CLASS) then
        Element.classList.add(SORTING_CLASS);
    end;
  end;

end;

procedure TGridPlugin.SetPageSize(APageSize: Integer; AutoReload: boolean);
begin
  FDatasetPlugin.PageSize := APageSize;
  if AutoReload then
    Load;
end;

function TGridPlugin.CalculateSortedItems: TSortedItems;
var
  Field: TField;
  ClassList: TJSDOMTokenList;
  CellElement: TJSElement;
  I: Integer;
  Fields: TJSArray;
  OrderInfo: TOrderInfo;
begin
  Fields := TJSArray.new;
  for I := 0 to FGrid.Columns.Count - 1 do
  begin
    if not IsOrdenableColumn(I) then
      Continue;
    Field := GetFieldByColumn(I);
    CellElement := FGrid.CellElements[I, 0];
    ClassList := CellElement.classList;

    if ClassList.contains(SORTING_ASC_CLASS) then
    begin
      OrderInfo.FieldName := Field.FieldName;
      OrderInfo.Ascending := True;
      Fields.push(OrderInfo);
    end
    else if ClassList.contains(SORTING_DESC_CLASS) then
    begin
      OrderInfo.FieldName := Field.FieldName;
      OrderInfo.Ascending := False;
      Fields.push(OrderInfo);
    end;
  end;

  SetLength(Result, Fields.Length);
  for I := 0 to Fields.Length - 1 do
    Result[I] := TOrderInfo(Fields[I]);
end;

end.
